Skip to content

Commit 6ea0db9

Browse files
committed
Use range types for all port ranges
Breaks backwards compatibility with relays.json (which is acceptable)
1 parent aa96f06 commit 6ea0db9

File tree

7 files changed

+100
-94
lines changed

7 files changed

+100
-94
lines changed

mullvad-api/src/relay_list.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
collections::BTreeMap,
1111
future::Future,
1212
net::{IpAddr, Ipv4Addr, Ipv6Addr},
13+
ops::RangeInclusive,
1314
time::Duration,
1415
};
1516

@@ -253,15 +254,28 @@ struct Wireguard {
253254
impl From<&Wireguard> for relay_list::WireguardEndpointData {
254255
fn from(wg: &Wireguard) -> Self {
255256
Self {
256-
port_ranges: wg.port_ranges.clone(),
257+
port_ranges: inclusive_range_from_pair_set(wg.port_ranges.clone()).collect(),
257258
ipv4_gateway: wg.ipv4_gateway,
258259
ipv6_gateway: wg.ipv6_gateway,
259-
shadowsocks_port_ranges: wg.shadowsocks_port_ranges.clone(),
260+
shadowsocks_port_ranges: inclusive_range_from_pair_set(
261+
wg.shadowsocks_port_ranges.clone(),
262+
)
263+
.collect(),
260264
udp2tcp_ports: vec![],
261265
}
262266
}
263267
}
264268

269+
fn inclusive_range_from_pair_set<T>(
270+
set: impl IntoIterator<Item = (T, T)>,
271+
) -> impl Iterator<Item = RangeInclusive<T>> {
272+
set.into_iter().map(inclusive_range_from_pair)
273+
}
274+
275+
fn inclusive_range_from_pair<T>(pair: (T, T)) -> RangeInclusive<T> {
276+
RangeInclusive::new(pair.0, pair.1)
277+
}
278+
265279
impl Wireguard {
266280
/// Consumes `self` and appends all its relays to `countries`.
267281
fn extract_relays(

mullvad-cli/src/cmds/relay.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ impl Relay {
669669
let is_valid_port = wireguard
670670
.port_ranges
671671
.into_iter()
672-
.any(|(first, last)| first <= specific_port && specific_port <= last);
672+
.any(|range| range.contains(&specific_port));
673673
if !is_valid_port {
674674
return Err(anyhow!("The specified port is invalid"));
675675
}

mullvad-management-interface/src/types/conversions/relay_list.rs

+21-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
net::{Ipv4Addr, Ipv6Addr},
3+
ops::RangeInclusive,
34
str::FromStr,
45
};
56

@@ -79,11 +80,11 @@ impl From<mullvad_types::relay_list::WireguardEndpointData> for proto::Wireguard
7980
}
8081
}
8182

82-
impl From<(u16, u16)> for proto::PortRange {
83-
fn from(range: (u16, u16)) -> Self {
83+
impl From<RangeInclusive<u16>> for proto::PortRange {
84+
fn from(range: RangeInclusive<u16>) -> Self {
8485
proto::PortRange {
85-
first: u32::from(range.0),
86-
last: u32::from(range.1),
86+
first: u32::from(*range.start()),
87+
last: u32::from(*range.end()),
8788
}
8889
}
8990
}
@@ -373,14 +374,8 @@ impl TryFrom<proto::WireguardEndpointData> for mullvad_types::relay_list::Wiregu
373374
let port_ranges = wireguard
374375
.port_ranges
375376
.into_iter()
376-
.map(|range| {
377-
let first = u16::try_from(range.first)
378-
.map_err(|_| FromProtobufTypeError::InvalidArgument("invalid wg port"))?;
379-
let last = u16::try_from(range.last)
380-
.map_err(|_| FromProtobufTypeError::InvalidArgument("invalid wg port"))?;
381-
Ok((first, last))
382-
})
383-
.collect::<Result<Vec<(u16, u16)>, FromProtobufTypeError>>()?;
377+
.map(RangeInclusive::try_from)
378+
.collect::<Result<Vec<_>, FromProtobufTypeError>>()?;
384379

385380
let ipv4_gateway = Ipv4Addr::from_str(&wireguard.ipv4_gateway)
386381
.map_err(|_| FromProtobufTypeError::InvalidArgument("Invalid IPv4 gateway"))?;
@@ -390,16 +385,8 @@ impl TryFrom<proto::WireguardEndpointData> for mullvad_types::relay_list::Wiregu
390385
let shadowsocks_port_ranges = wireguard
391386
.shadowsocks_port_ranges
392387
.into_iter()
393-
.map(|range| {
394-
let first = u16::try_from(range.first).map_err(|_| {
395-
FromProtobufTypeError::InvalidArgument("invalid shadowsocks port")
396-
})?;
397-
let last = u16::try_from(range.last).map_err(|_| {
398-
FromProtobufTypeError::InvalidArgument("invalid shadowsocks port")
399-
})?;
400-
Ok((first, last))
401-
})
402-
.collect::<Result<Vec<(u16, u16)>, FromProtobufTypeError>>()?;
388+
.map(RangeInclusive::try_from)
389+
.collect::<Result<Vec<_>, FromProtobufTypeError>>()?;
403390

404391
let udp2tcp_ports = wireguard
405392
.udp2tcp_ports
@@ -419,3 +406,15 @@ impl TryFrom<proto::WireguardEndpointData> for mullvad_types::relay_list::Wiregu
419406
})
420407
}
421408
}
409+
410+
impl TryFrom<proto::PortRange> for RangeInclusive<u16> {
411+
type Error = FromProtobufTypeError;
412+
413+
fn try_from(range: proto::PortRange) -> Result<Self, Self::Error> {
414+
let first = u16::try_from(range.first)
415+
.map_err(|_| FromProtobufTypeError::InvalidArgument("invalid port"))?;
416+
let last = u16::try_from(range.last)
417+
.map_err(|_| FromProtobufTypeError::InvalidArgument("invalid port"))?;
418+
Ok(first..=last)
419+
}
420+
}

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

+39-47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! This module contains various helper functions for the relay selector implementation.
22
3-
use std::net::{IpAddr, SocketAddr};
3+
use std::{
4+
net::{IpAddr, SocketAddr},
5+
ops::{RangeBounds, RangeInclusive},
6+
};
47

58
use mullvad_types::{
69
constraints::Constraint,
@@ -18,12 +21,10 @@ use crate::SelectedObfuscator;
1821

1922
/// Port ranges available for WireGuard relays that have extra IPs for Shadowsocks.
2023
/// For relays that have no additional IPs, only ports provided by the relay list are available.
21-
const SHADOWSOCKS_EXTRA_PORT_RANGES: &[(u16, u16)] = &[(1, u16::MAX)];
24+
const SHADOWSOCKS_EXTRA_PORT_RANGES: &[RangeInclusive<u16>] = &[1..=u16::MAX];
2225

2326
#[derive(thiserror::Error, Debug)]
2427
pub enum Error {
25-
#[error("Port selection algorithm is broken")]
26-
PortSelectionAlgorithm,
2728
#[error("Found no valid port matching the selected settings")]
2829
NoMatchingPort,
2930
}
@@ -107,7 +108,7 @@ fn get_udp2tcp_obfuscator_port(
107108

108109
pub fn get_shadowsocks_obfuscator(
109110
settings: &ShadowsocksSettings,
110-
non_extra_port_ranges: &[(u16, u16)],
111+
non_extra_port_ranges: &[RangeInclusive<u16>],
111112
relay: Relay,
112113
endpoint: &MullvadWireguardEndpoint,
113114
) -> Result<SelectedObfuscator, Error> {
@@ -135,9 +136,9 @@ pub fn get_shadowsocks_obfuscator(
135136
/// Return an obfuscation config for the wireguard server at `wg_in_addr` or one of `extra_in_addrs`
136137
/// (unless empty). `wg_in_addr_port_ranges` contains all valid ports for `wg_in_addr`, and
137138
/// `SHADOWSOCKS_EXTRA_PORT_RANGES` contains valid ports for `extra_in_addrs`.
138-
fn get_shadowsocks_obfuscator_inner(
139+
fn get_shadowsocks_obfuscator_inner<R: RangeBounds<u16> + Iterator<Item = u16> + Clone>(
139140
wg_in_addr: IpAddr,
140-
wg_in_addr_port_ranges: &[(u16, u16)],
141+
wg_in_addr_port_ranges: &[R],
141142
extra_in_addrs: &[IpAddr],
142143
desired_port: Constraint<u16>,
143144
) -> Result<SocketAddr, Error> {
@@ -154,23 +155,27 @@ fn get_shadowsocks_obfuscator_inner(
154155
.copied()
155156
.unwrap_or(wg_in_addr);
156157

157-
let port_ranges = if extra_in_addrs.is_empty() {
158-
wg_in_addr_port_ranges
158+
let selected_port = if extra_in_addrs.is_empty() {
159+
desired_port_from_range(wg_in_addr_port_ranges, desired_port)
159160
} else {
160-
SHADOWSOCKS_EXTRA_PORT_RANGES
161-
};
161+
desired_port_from_range(SHADOWSOCKS_EXTRA_PORT_RANGES, desired_port)
162+
}?;
163+
164+
Ok(SocketAddr::from((in_ip, selected_port)))
165+
}
162166

163-
let selected_port = match desired_port {
167+
fn desired_port_from_range<R: RangeBounds<u16> + Iterator<Item = u16> + Clone>(
168+
port_ranges: &[R],
169+
desired_port: Constraint<u16>,
170+
) -> Result<u16, Error> {
171+
match desired_port {
164172
// Selected a specific, in-range port
165-
Constraint::Only(port) if super::helpers::port_in_range(port, port_ranges) => Some(port),
173+
Constraint::Only(port) if port_in_range(port, port_ranges) => Ok(port),
166174
// Selected a specific, out-of-range port
167-
Constraint::Only(_port) => None,
175+
Constraint::Only(_port) => Err(Error::NoMatchingPort),
168176
// Selected no specific port
169-
Constraint::Any => super::helpers::select_random_port(port_ranges).ok(),
177+
Constraint::Any => select_random_port(port_ranges),
170178
}
171-
.ok_or(Error::NoMatchingPort)?;
172-
173-
Ok(SocketAddr::from((in_ip, selected_port)))
174179
}
175180

176181
/// Selects a random port number from a list of provided port ranges.
@@ -185,45 +190,32 @@ fn get_shadowsocks_obfuscator_inner(
185190
///
186191
/// # Returns
187192
/// - A randomly selected port number within the given ranges.
188-
///
189-
/// # Panic
190-
/// - If port ranges contains no ports, this function panics.
191-
pub fn select_random_port(port_ranges: &[(u16, u16)]) -> Result<u16, Error> {
192-
let get_port_amount = |range: &(u16, u16)| -> u64 { 1 + range.1 as u64 - range.0 as u64 };
193-
let port_amount: u64 = port_ranges.iter().map(get_port_amount).sum();
194-
195-
if port_amount < 1 {
196-
return Err(Error::PortSelectionAlgorithm);
197-
}
198-
199-
let mut port_index = rand::thread_rng().gen_range(0..port_amount);
200-
201-
for range in port_ranges.iter() {
202-
let ports_in_range = get_port_amount(range);
203-
if port_index < ports_in_range {
204-
return Ok(port_index as u16 + range.0);
205-
}
206-
port_index -= ports_in_range;
207-
}
208-
Err(Error::PortSelectionAlgorithm)
209-
}
210-
211-
pub fn port_in_range(port: u16, port_ranges: &[(u16, u16)]) -> bool {
193+
/// - An error if `port_ranges` is empty.
194+
pub fn select_random_port<R: RangeBounds<u16> + Iterator<Item = u16> + Clone>(
195+
port_ranges: &[R],
196+
) -> Result<u16, Error> {
212197
port_ranges
213198
.iter()
214-
.any(|range| (range.0 <= port && port <= range.1))
199+
.cloned()
200+
.flatten()
201+
.choose(&mut rand::thread_rng())
202+
.ok_or(Error::NoMatchingPort)
203+
}
204+
205+
pub fn port_in_range<R: RangeBounds<u16>>(port: u16, port_ranges: &[R]) -> bool {
206+
port_ranges.iter().any(|range| range.contains(&port))
215207
}
216208

217209
#[cfg(test)]
218210
mod tests {
219211
use super::{get_shadowsocks_obfuscator_inner, port_in_range, SHADOWSOCKS_EXTRA_PORT_RANGES};
220212
use mullvad_types::constraints::Constraint;
221-
use std::net::IpAddr;
213+
use std::{net::IpAddr, ops::RangeInclusive};
222214

223215
/// Test whether select ports are available when relay has no extra IPs
224216
#[test]
225217
fn test_shadowsocks_no_extra_addrs() {
226-
const PORT_RANGES: &[(u16, u16)] = &[(100, 200), (1000, 2000)];
218+
const PORT_RANGES: &[RangeInclusive<u16>] = &[100..=200, 1000..=2000];
227219
const WITHIN_RANGE_PORT: u16 = 100;
228220
const OUT_OF_RANGE_PORT: u16 = 1;
229221
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();
@@ -267,7 +259,7 @@ mod tests {
267259
/// All ports should be available when relay has extra IPs, and only extra IPs should be used
268260
#[test]
269261
fn test_shadowsocks_extra_addrs() {
270-
const PORT_RANGES: &[(u16, u16)] = &[(100, 200), (1000, 2000)];
262+
const PORT_RANGES: &[RangeInclusive<u16>] = &[100..=200, 1000..=2000];
271263
const OUT_OF_RANGE_PORT: u16 = 1;
272264
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();
273265

@@ -312,7 +304,7 @@ mod tests {
312304
/// Extra addresses that belong to the wrong IP family should be ignored
313305
#[test]
314306
fn test_shadowsocks_irrelevant_extra_addrs() {
315-
const PORT_RANGES: &[(u16, u16)] = &[(100, 200), (1000, 2000)];
307+
const PORT_RANGES: &[RangeInclusive<u16>] = &[100..=200, 1000..=2000];
316308
const IN_RANGE_PORT: u16 = 100;
317309
const OUT_OF_RANGE_PORT: u16 = 1;
318310
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();

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

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! This module is responsible for filtering the whole relay list based on queries.
2-
use std::collections::HashSet;
2+
use std::{collections::HashSet, ops::RangeInclusive};
33

44
use mullvad_types::{
55
constraints::{Constraint, Match},
@@ -155,7 +155,7 @@ fn filter_on_obfuscation(
155155

156156
/// Returns whether `relay` satisfies the Shadowsocks filter posed by `port`.
157157
fn filter_on_shadowsocks(
158-
port_ranges: &[(u16, u16)],
158+
port_ranges: &[RangeInclusive<u16>],
159159
ip_version: &Constraint<IpVersion>,
160160
settings: &ShadowsocksSettings,
161161
relay: &Relay,
@@ -177,9 +177,7 @@ fn filter_on_shadowsocks(
177177
.find(|&&addr| IpVersion::from(addr) == ip_version);
178178

179179
filtered_extra_addrs.is_some()
180-
|| port_ranges
181-
.iter()
182-
.any(|(begin, end)| (*begin..=*end).contains(desired_port))
180+
|| port_ranges.iter().any(|range| range.contains(desired_port))
183181
}
184182

185183
// Otherwise, any relay works.

mullvad-relay-selector/tests/relay_selector.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,16 @@ static RELAYS: Lazy<RelayList> = Lazy::new(|| RelayList {
161161
},
162162
wireguard: WireguardEndpointData {
163163
port_ranges: vec![
164-
(53, 53),
165-
(443, 443),
166-
(4000, 33433),
167-
(33565, 51820),
168-
(52000, 60000),
164+
53..=53,
165+
443..=443,
166+
4000..=33433,
167+
33565..=51820,
168+
52000..=60000,
169169
],
170170
ipv4_gateway: "10.64.0.1".parse().unwrap(),
171171
ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(),
172172
udp2tcp_ports: vec![],
173-
shadowsocks_port_ranges: vec![(100, 200), (1000, 2000)],
173+
shadowsocks_port_ranges: vec![100..=200, 1000..=2000],
174174
},
175175
});
176176

@@ -506,16 +506,16 @@ fn test_wireguard_entry() {
506506
},
507507
wireguard: WireguardEndpointData {
508508
port_ranges: vec![
509-
(53, 53),
510-
(443, 443),
511-
(4000, 33433),
512-
(33565, 51820),
513-
(52000, 60000),
509+
53..=53,
510+
443..=443,
511+
4000..=33433,
512+
33565..=51820,
513+
52000..=60000,
514514
],
515515
ipv4_gateway: "10.64.0.1".parse().unwrap(),
516516
ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(),
517517
udp2tcp_ports: vec![],
518-
shadowsocks_port_ranges: vec![(100, 200), (1000, 2000)],
518+
shadowsocks_port_ranges: vec![100..=200, 1000..=2000],
519519
},
520520
};
521521

@@ -1175,7 +1175,7 @@ fn test_include_in_country() {
11751175
shadowsocks: vec![],
11761176
},
11771177
wireguard: WireguardEndpointData {
1178-
port_ranges: vec![(53, 53), (4000, 33433), (33565, 51820), (52000, 60000)],
1178+
port_ranges: vec![53..=53, 4000..=33433, 33565..=51820, 52000..=60000],
11791179
ipv4_gateway: "10.64.0.1".parse().unwrap(),
11801180
ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(),
11811181
udp2tcp_ports: vec![],
@@ -1381,7 +1381,7 @@ fn test_daita() {
13811381
shadowsocks: vec![],
13821382
},
13831383
wireguard: WireguardEndpointData {
1384-
port_ranges: vec![(53, 53), (4000, 33433), (33565, 51820), (52000, 60000)],
1384+
port_ranges: vec![53..=53, 4000..=33433, 33565..=51820, 52000..=60000],
13851385
ipv4_gateway: "10.64.0.1".parse().unwrap(),
13861386
ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(),
13871387
shadowsocks_port_ranges: vec![],

0 commit comments

Comments
 (0)