mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -431,6 +431,8 @@ impl TestServer {
|
||||
relay_public_addr,
|
||||
StepRng::new(0, 0),
|
||||
&mut Registry::default(),
|
||||
49152,
|
||||
65535,
|
||||
),
|
||||
id_to_port: Default::default(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user