mirror of
https://github.com/ekzhang/bore.git
synced 2025-12-16 03:47:50 +01:00
Use random ports when port number is 0 (#79)
* Use random ports when port number is 0 * Add support for --max-port CLI option * Fix typo * Fix another typo * Update README
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
//! Server implementation for the `bore` service.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{io, net::SocketAddr, ops::RangeInclusive, sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
@@ -17,8 +15,8 @@ use crate::shared::{proxy, ClientMessage, Delimited, ServerMessage, CONTROL_PORT
|
||||
|
||||
/// State structure for the server.
|
||||
pub struct Server {
|
||||
/// The minimum TCP port that can be forwarded.
|
||||
min_port: u16,
|
||||
/// Range of TCP ports that can be forwarded.
|
||||
port_range: RangeInclusive<u16>,
|
||||
|
||||
/// Optional secret used to authenticate clients.
|
||||
auth: Option<Authenticator>,
|
||||
@@ -29,9 +27,10 @@ pub struct Server {
|
||||
|
||||
impl Server {
|
||||
/// Create a new server with a specified minimum port number.
|
||||
pub fn new(min_port: u16, secret: Option<&str>) -> Self {
|
||||
pub fn new(port_range: RangeInclusive<u16>, secret: Option<&str>) -> Self {
|
||||
assert!(!port_range.is_empty(), "must provide at least one port");
|
||||
Server {
|
||||
min_port,
|
||||
port_range,
|
||||
conns: Arc::new(DashMap::new()),
|
||||
auth: secret.map(Authenticator::new),
|
||||
}
|
||||
@@ -61,6 +60,43 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_listener(&self, port: u16) -> Result<TcpListener, &'static str> {
|
||||
let try_bind = |port: u16| async move {
|
||||
TcpListener::bind(("0.0.0.0", port))
|
||||
.await
|
||||
.map_err(|err| match err.kind() {
|
||||
io::ErrorKind::AddrInUse => "port already in use",
|
||||
io::ErrorKind::PermissionDenied => "permission denied",
|
||||
_ => "failed to bind to port",
|
||||
})
|
||||
};
|
||||
if port > 0 {
|
||||
// Client requests a specific port number.
|
||||
if !self.port_range.contains(&port) {
|
||||
return Err("client port number not in allowed range");
|
||||
}
|
||||
try_bind(port).await
|
||||
} else {
|
||||
// Client requests any available port in range.
|
||||
//
|
||||
// In this case, we bind to 150 random port numbers. We choose this value because in
|
||||
// order to find a free port with probability at least 1-δ, when ε proportion of the
|
||||
// ports are currently available, it suffices to check approximately -2 ln(δ) / ε
|
||||
// independently and uniformly chosen ports (up to a second-order term in ε).
|
||||
//
|
||||
// Checking 150 times gives us 99.999% success at utilizing 85% of ports under these
|
||||
// conditions, when ε=0.15 and δ=0.00001.
|
||||
for _ in 0..150 {
|
||||
let port = fastrand::u16(self.port_range.clone());
|
||||
match try_bind(port).await {
|
||||
Ok(listener) => return Ok(listener),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
Err("failed to find an available port")
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(&self, stream: TcpStream) -> Result<()> {
|
||||
let mut stream = Delimited::new(stream);
|
||||
if let Some(auth) = &self.auth {
|
||||
@@ -77,22 +113,15 @@ impl Server {
|
||||
Ok(())
|
||||
}
|
||||
Some(ClientMessage::Hello(port)) => {
|
||||
if port != 0 && port < self.min_port {
|
||||
warn!(?port, "client port number too low");
|
||||
return Ok(());
|
||||
}
|
||||
info!(?port, "new client");
|
||||
let listener = match TcpListener::bind(("0.0.0.0", port)).await {
|
||||
let listener = match self.create_listener(port).await {
|
||||
Ok(listener) => listener,
|
||||
Err(_) => {
|
||||
warn!(?port, "could not bind to local port");
|
||||
stream
|
||||
.send(ServerMessage::Error("port already in use".into()))
|
||||
.await?;
|
||||
Err(err) => {
|
||||
stream.send(ServerMessage::Error(err.into())).await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let port = listener.local_addr()?.port();
|
||||
info!(?port, "new client");
|
||||
stream.send(ServerMessage::Hello(port)).await?;
|
||||
|
||||
loop {
|
||||
@@ -133,16 +162,7 @@ impl Server {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
warn!("unexpected EOF");
|
||||
Ok(())
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
Server::new(1024, None)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user