feat(relay): allow configuration for lowest and highest allocation port (#1921)

This PR allows the TURN allocation binding to be optionally configured
by `TURN_LOWEST_PORT` and `TURN_HIGHEST_PORT` environment variables.

This will allow client app developers to test their apps against a
fully-working local development cluster in Docker Desktop for
Linux/macOS/Windows, allowing us to remove the PortalMock, Connlib Mock,
and SwiftMock codepaths entirely.

cc @roop @pratikvelani
This commit is contained in:
Jamil
2023-08-18 13:04:26 -07:00
committed by GitHub
parent 79a24ca9cf
commit bf2d794064
4 changed files with 49 additions and 14 deletions

View File

@@ -21,7 +21,7 @@ services:
retries: 5
timeout: 5s
ports:
- 5432:5432
- 5432:5432/tcp
networks:
- app
@@ -181,6 +181,8 @@ services:
environment:
PUBLIC_IP4_ADDR: 172.28.0.101
PUBLIC_IP6_ADDR: fcff:3990:3990::101
LOWEST_PORT: 55555
HIGHEST_PORT: 55666
PORTAL_WS_URL: "ws://api:8081/"
PORTAL_TOKEN: "SFMyNTY.g2gDaAJtAAAAJDcyODZiNTNkLTA3M2UtNGM0MS05ZmYxLWNjODQ1MWRhZDI5OW0AAABARVg3N0dhMEhLSlVWTGdjcE1yTjZIYXRkR25mdkFEWVFyUmpVV1d5VHFxdDdCYVVkRVUzbzktRmJCbFJkSU5JS24GAFSzb0KJAWIAAVGA.waeGE26tbgkgIcMrWyck0ysv9SHIoHr0zqoM3wao84M"
RUST_LOG: "debug"
@@ -200,6 +202,13 @@ services:
depends_on:
api:
condition: 'service_healthy'
ports:
# XXX: Only 111 ports are used for local dev / testing because Docker Desktop
# allocates a userland proxy process for each forwarded port X_X.
#
# Large ranges here will bring your machine to its knees.
- "55555-55666:55555-55666/udp"
- 3478:3478/udp
networks:
app:
ipv4_address: 172.28.0.101

View File

@@ -32,10 +32,16 @@ struct Args {
#[arg(long, env)]
public_ip6_addr: Option<Ipv6Addr>,
/// The address of the local interface where we should serve the prometheus metrics.
///
/// The metrics will be available at `http://<metrics_addr>/metrics`.
#[arg(long, env)]
metrics_addr: Option<SocketAddr>,
// See https://www.rfc-editor.org/rfc/rfc8656.html#name-allocations
/// The lowest port used for TURN allocations.
#[arg(long, env, default_value = "49152")]
lowest_port: u16,
/// The highest port used for TURN allocations.
#[arg(long, env, default_value = "65535")]
highest_port: u16,
/// The websocket URL of the portal server to connect to.
#[arg(long, env, default_value = "wss://api.firezone.dev")]
portal_ws_url: Url,
@@ -80,7 +86,13 @@ async fn main() -> Result<()> {
};
let mut metric_registry = Registry::with_prefix("relay");
let server = Server::new(public_addr, make_rng(args.rng_seed), &mut metric_registry);
let server = Server::new(
public_addr,
make_rng(args.rng_seed),
&mut metric_registry,
args.lowest_port,
args.highest_port,
);
let channel = if let Some(token) = args.portal_token {
let mut url = args.portal_ws_url.clone();

View File

@@ -46,7 +46,7 @@ use uuid::Uuid;
/// Thus, 3 out of the 5 components of a "5-tuple" are unique to an instance of [`Server`] and
/// we can index data simply by the sender's [`SocketAddr`].
///
/// Additionally, we assume to have complete ownership over the port range `LOWEST_PORT` - `HIGHEST_PORT`.
/// Additionally, we assume to have complete ownership over the port range `lowest_port` - `highest_port`.
pub struct Server<R> {
decoder: client_message::Decoder,
encoder: MessageEncoder<Attribute>,
@@ -58,6 +58,9 @@ pub struct Server<R> {
clients_by_allocation: HashMap<AllocationId, SocketAddr>,
allocations_by_port: HashMap<u16, AllocationId>,
lowest_port: u16,
highest_port: u16,
channels_by_number: HashMap<u16, Channel>,
channel_numbers_by_peer: HashMap<SocketAddr, u16>,
@@ -133,12 +136,6 @@ impl fmt::Display for AllocationId {
/// See <https://www.rfc-editor.org/rfc/rfc8656#name-requested-transport>.
const UDP_TRANSPORT: u8 = 17;
const LOWEST_PORT: u16 = 49152;
const HIGHEST_PORT: u16 = 65535;
/// The maximum number of ports available for allocation.
const MAX_AVAILABLE_PORTS: u16 = HIGHEST_PORT - LOWEST_PORT;
/// The duration of a channel binding.
///
/// See <https://www.rfc-editor.org/rfc/rfc8656#name-channels-2>.
@@ -148,7 +145,16 @@ impl<R> Server<R>
where
R: Rng,
{
pub fn new(public_address: impl Into<IpStack>, mut rng: R, registry: &mut Registry) -> Self {
/// Although not explicitly recommended by the RFC, we allow the user to pass a custom
/// port allocation range. This is helpful when debugging, running NATed (such as in Docker),
/// or when deploying in production where the default range conflicts with other services.
pub fn new(
public_address: impl Into<IpStack>,
mut rng: R,
registry: &mut Registry,
lowest_port: u16,
highest_port: u16,
) -> Self {
// TODO: Validate that local IP isn't multicast / loopback etc.
let allocations_gauge = Gauge::default();
@@ -179,6 +185,8 @@ where
allocations: Default::default(),
clients_by_allocation: Default::default(),
allocations_by_port: Default::default(),
lowest_port,
highest_port,
channels_by_number: Default::default(),
channel_numbers_by_peer: Default::default(),
pending_commands: Default::default(),
@@ -441,7 +449,7 @@ where
return Err(error_response(AllocationMismatch, &request));
}
if self.allocations_by_port.len() == MAX_AVAILABLE_PORTS as usize {
if self.allocations_by_port.len() == self.max_available_ports() as usize {
return Err(error_response(InsufficientCapacity, &request));
}
@@ -766,12 +774,12 @@ where
// First, find an unused port.
assert!(
self.allocations_by_port.len() < MAX_AVAILABLE_PORTS as usize,
self.allocations_by_port.len() < self.max_available_ports() as usize,
"No more ports available; this would loop forever"
);
let port = loop {
let candidate = self.rng.gen_range(LOWEST_PORT..HIGHEST_PORT);
let candidate = self.rng.gen_range(self.lowest_port..self.highest_port);
if !self.allocations_by_port.contains_key(&candidate) {
break candidate;
@@ -792,6 +800,10 @@ where
}
}
fn max_available_ports(&self) -> u16 {
self.highest_port - self.lowest_port
}
fn create_channel_binding(
&mut self,
requested_channel: u16,

View File

@@ -431,6 +431,8 @@ impl TestServer {
relay_public_addr,
StepRng::new(0, 0),
&mut Registry::default(),
49152,
65535,
),
id_to_port: Default::default(),
}