diff --git a/docker-compose.yml b/docker-compose.yml index 2afd0b33f..6baa7c41e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/rust/relay/src/main.rs b/rust/relay/src/main.rs index 0c0b5d8c1..ad1ed49ed 100644 --- a/rust/relay/src/main.rs +++ b/rust/relay/src/main.rs @@ -32,10 +32,16 @@ struct Args { #[arg(long, env)] public_ip6_addr: Option, /// The address of the local interface where we should serve the prometheus metrics. - /// /// The metrics will be available at `http:///metrics`. #[arg(long, env)] metrics_addr: Option, + // 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(); diff --git a/rust/relay/src/server.rs b/rust/relay/src/server.rs index 8e42a7762..c3e73b87d 100644 --- a/rust/relay/src/server.rs +++ b/rust/relay/src/server.rs @@ -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 { decoder: client_message::Decoder, encoder: MessageEncoder, @@ -58,6 +58,9 @@ pub struct Server { clients_by_allocation: HashMap, allocations_by_port: HashMap, + lowest_port: u16, + highest_port: u16, + channels_by_number: HashMap, channel_numbers_by_peer: HashMap, @@ -133,12 +136,6 @@ impl fmt::Display for AllocationId { /// See . 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 . @@ -148,7 +145,16 @@ impl Server where R: Rng, { - pub fn new(public_address: impl Into, 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, + 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, diff --git a/rust/relay/tests/regression.rs b/rust/relay/tests/regression.rs index 6436a7fcd..9ef9f6c19 100644 --- a/rust/relay/tests/regression.rs +++ b/rust/relay/tests/regression.rs @@ -431,6 +431,8 @@ impl TestServer { relay_public_addr, StepRng::new(0, 0), &mut Registry::default(), + 49152, + 65535, ), id_to_port: Default::default(), }