aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgilles henaux <gill.henaux@gmail.com>2023-02-20 21:47:41 +0100
committergilles henaux <gill.henaux@gmail.com>2023-02-20 21:47:41 +0100
commit6d85439ea69cecb9635a4eee64b4d784312ec3f5 (patch)
tree505d623d308170346ed31bfeae86a181d6a99229
parent81527b24060ab73f5bdaad252a2d62fd42f36dd7 (diff)
[client & server] Hanlde IPv6 addresses properly: rework ConnectionConfiguration and ServerConfiguration
-rw-r--r--src/client/connection.rs164
-rw-r--r--src/server.rs101
-rw-r--r--src/server/certificate.rs23
3 files changed, 204 insertions, 84 deletions
diff --git a/src/client/connection.rs b/src/client/connection.rs
index f7538f6..05ab185 100644
--- a/src/client/connection.rs
+++ b/src/client/connection.rs
@@ -1,4 +1,9 @@
-use std::{collections::HashMap, error::Error, net::SocketAddr, sync::Arc};
+use std::{
+ collections::HashMap,
+ error::Error,
+ net::{AddrParseError, IpAddr, SocketAddr},
+ sync::Arc,
+};
use bevy::prelude::{error, info};
use bytes::Bytes;
@@ -45,10 +50,9 @@ pub struct ConnectionLostEvent {
/// Configuration of a client connection, used when connecting to a server
#[derive(Debug, Deserialize, Clone)]
pub struct ConnectionConfiguration {
- server_host: String,
- server_port: u16,
- local_bind_host: String,
- local_bind_port: u16,
+ server_addr: SocketAddr,
+ server_hostname: String,
+ local_bind_addr: SocketAddr,
}
impl ConnectionConfiguration {
@@ -56,33 +60,133 @@ impl ConnectionConfiguration {
///
/// # Arguments
///
- /// * `server_host` - Address of the server
- /// * `server_port` - Port that the server is listening on
- /// * `local_bind_host` - Local address to bind to, which should usually be a wildcard address like `0.0.0.0` or `[::]`, which allow communication with any reachable IPv4 or IPv6 address. See [`quinn::endpoint::Endpoint`] for more precision
- /// * `local_bind_port` - Local port to bind to. Use 0 to get an OS-assigned port.. See [`quinn::endpoint::Endpoint`] for more precision
+ /// * `server_addr_str` - IP address and port of the server
+ /// * `local_bind_addr_str` - Local address and port to bind to separated by `:`. The address should usually be a wildcard like `0.0.0.0` (for an IPv4) or `[::]` (for an IPv6), which allow communication with any reachable IPv4 or IPv6 address. See [`std::net::SocketAddrV4`] and [`std::net::SocketAddrV6`] or [`quinn::endpoint::Endpoint`] for more precision. For the local port to bind to, use 0 to get an OS-assigned port.
///
/// # Examples
///
+ /// Connect to an IPv4 server hosted on localhost (127.0.0.1), which is listening on port 6000. Use 0 as a local bind port to let the OS assign a port.
+ /// ```
+ /// use bevy_quinnet::client::connection::ConnectionConfiguration;
+ /// let config = ConnectionConfiguration::from_strings(
+ /// "127.0.0.1:6000",
+ /// "0.0.0.0:0"
+ /// );
+ /// ```
+ /// Connect to an IPv6 server hosted on localhost (::1), which is listening on port 6000. Use 0 as a local bind port to let the OS assign a port.
/// ```
/// use bevy_quinnet::client::connection::ConnectionConfiguration;
- /// let config = ConnectionConfiguration::new(
- /// "127.0.0.1".to_string(),
- /// 6000,
- /// "0.0.0.0".to_string(),
- /// 0,
- /// );
+ /// let config = ConnectionConfiguration::from_strings(
+ /// "[::1]:6000",
+ /// "[::]:0"
+ /// );
/// ```
- pub fn new(
- server_host: String,
+ pub fn from_strings(
+ server_addr_str: &str,
+ local_bind_addr_str: &str,
+ ) -> Result<Self, AddrParseError> {
+ let server_addr = server_addr_str.parse()?;
+ let local_bind_addr = local_bind_addr_str.parse()?;
+ Ok(Self::from_addrs(server_addr, local_bind_addr))
+ }
+
+ /// Same as [`ConnectionConfiguration::from_strings`], but with an additional `server_hostname` for certificate verification if it is not just the server IP.
+ pub fn from_strings_with_name(
+ server_addr_str: &str,
+ server_hostname: String,
+ local_bind_addr_str: &str,
+ ) -> Result<Self, AddrParseError> {
+ Ok(Self::from_addrs_with_name(
+ server_addr_str.parse()?,
+ server_hostname,
+ local_bind_addr_str.parse()?,
+ ))
+ }
+
+ /// Creates a new ConnectionConfiguration
+ ///
+ /// # Arguments
+ ///
+ /// * `server_ip` - IP address of the server
+ /// * `server_port` - Port of the server
+ /// * `local_bind_ip` - Local IP address to bind to. The address should usually be a wildcard like `0.0.0.0` (for an IPv4) or `0:0:0:0:0:0:0:0` (for an IPv6), which allow communication with any reachable IPv4 or IPv6 address. See [`std::net::Ipv4Addr`] and [`std::net::Ipv6Addr`] for more precision.
+ /// * `local_bind_port` - Local port to bind to. Use 0 to get an OS-assigned port.
+ ///
+ /// # Examples
+ ///
+ /// Connect to an IPv4 server hosted on localhost (127.0.0.1), which is listening on port 6000. Use 0 as a local bind port to let the OS assign a port.
+ /// ```
+ /// use bevy_quinnet::client::connection::ConnectionConfiguration;
+ /// let config = ConnectionConfiguration::from_ips(
+ /// IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
+ /// 6000,
+ /// IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
+ /// 0
+ /// );
+ /// ```
+ pub fn from_ips(
+ server_ip: IpAddr,
+ server_port: u16,
+ local_bind_ip: IpAddr,
+ local_bind_port: u16,
+ ) -> Self {
+ Self::from_addrs(
+ SocketAddr::new(server_ip, server_port),
+ SocketAddr::new(local_bind_ip, local_bind_port),
+ )
+ }
+
+ /// Same as [`ConnectionConfiguration::from_ips`], but with an additional `server_hostname` for certificate verification if it is not just the server IP.
+ pub fn from_ips_with_name(
+ server_ip: IpAddr,
server_port: u16,
- local_bind_host: String,
+ server_hostname: String,
+ local_bind_ip: IpAddr,
local_bind_port: u16,
) -> Self {
+ Self::from_addrs_with_name(
+ SocketAddr::new(server_ip, server_port),
+ server_hostname,
+ SocketAddr::new(local_bind_ip, local_bind_port),
+ )
+ }
+
+ /// Creates a new ConnectionConfiguration
+ ///
+ /// # Arguments
+ ///
+ /// * `server_addr` - IP address and port of the server
+ /// * `local_bind_addr` - Local address and port to bind to. For the local port to bind to, use 0 to get an OS-assigned port.
+ ///
+ /// # Examples
+ ///
+ /// Connect to an IPv4 server hosted on localhost (127.0.0.1), which is listening on port 6000. Use 0 as a local bind port to let the OS assign a port.
+ /// ```
+ /// use bevy_quinnet::client::connection::ConnectionConfiguration;
+ /// use std::{net::{IpAddr, Ipv4Addr, SocketAddr}};
+ /// let config = ConnectionConfiguration::from_addrs(
+ /// SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6000),
+ /// SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0),
+ /// );
+ /// ```
+ pub fn from_addrs(server_addr: SocketAddr, local_bind_addr: SocketAddr) -> Self {
+ Self {
+ server_addr,
+ server_hostname: server_addr.ip().to_string(),
+ local_bind_addr,
+ }
+ }
+
+ /// Same as [`ConnectionConfiguration::from_addrs`], but with an additional `server_hostname` for certificate verification if it is not just the server IP.
+ pub fn from_addrs_with_name(
+ server_addr: SocketAddr,
+ server_hostname: String,
+ local_bind_addr: SocketAddr,
+ ) -> Self {
Self {
- server_host,
- server_port,
- local_bind_host,
- local_bind_port,
+ server_addr,
+ server_hostname,
+ local_bind_addr,
}
}
}
@@ -380,28 +484,20 @@ pub(crate) async fn connection_task(
close_recv: broadcast::Receiver<()>,
bytes_from_server_send: mpsc::Sender<Bytes>,
) {
- let server_adr_str = format!("{}:{}", config.server_host, config.server_port);
- let srv_host = config.server_host.clone();
- let local_bind_adr = format!("{}:{}", config.local_bind_host, config.local_bind_port);
-
info!(
"Connection {} trying to connect to server on: {} ...",
- connection_id, server_adr_str
+ connection_id, config.server_addr
);
- let server_addr: SocketAddr = server_adr_str
- .parse()
- .expect("Failed to parse server address");
-
let client_cfg = configure_client(cert_mode, to_sync_client_send.clone())
.expect("Failed to configure client");
- let mut endpoint = Endpoint::client(local_bind_adr.parse().unwrap())
- .expect("Failed to create client endpoint");
+ let mut endpoint =
+ Endpoint::client(config.local_bind_addr).expect("Failed to create client endpoint");
endpoint.set_default_client_config(client_cfg);
let connection = endpoint
- .connect(server_addr, &srv_host) // TODO Clean: error handling
+ .connect(config.server_addr, &config.server_hostname)
.expect("Failed to connect: configuration error")
.await;
match connection {
diff --git a/src/server.rs b/src/server.rs
index c2ab409..ae3bd82 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,6 +1,6 @@
use std::{
collections::{HashMap, HashSet},
- net::SocketAddr,
+ net::{AddrParseError, IpAddr, SocketAddr},
sync::Arc,
time::Duration,
};
@@ -52,50 +52,76 @@ pub struct ConnectionLostEvent {
pub id: ClientId,
}
-/// Configuration of the server, used when the server starts
-///
-/// # Examples
-///
-/// ```
-/// use bevy_quinnet::server::ServerConfigurationData;
-/// let config = ServerConfigurationData::new(
-/// "127.0.0.1".to_string(),
-/// 6000,
-/// "0.0.0.0".to_string());
-/// ```
+/// Configuration of the server, used when the server starts an Endpoint
#[derive(Debug, Deserialize, Clone)]
-pub struct ServerConfigurationData {
- host: String,
- port: u16,
- local_bind_host: String,
+pub struct ServerConfiguration {
+ local_bind_addr: SocketAddr,
}
-impl ServerConfigurationData {
- /// Creates a new ServerConfigurationData
+impl ServerConfiguration {
+ /// Creates a new ServerConfiguration
///
/// # Arguments
///
- /// * `host` - Address of the server
- /// * `port` - Port that the server is listening on
- /// * `local_bind_host` - Local address to bind to, which should usually be a wildcard address like `0.0.0.0` or `[::]`, which allow communication with any reachable IPv4 or IPv6 address. See [`quinn::endpoint::Endpoint`] for more precision
+ /// * `local_bind_addr_str` - Local address and port to bind to separated by `:`. The address should usually be a wildcard like `0.0.0.0` (for an IPv4) or `[::]` (for an IPv6), which allow communication with any reachable IPv4 or IPv6 address. See [`std::net::SocketAddrV4`] and [`std::net::SocketAddrV6`] or [`quinn::endpoint::Endpoint`] for more precision.
///
/// # Examples
///
+ /// Listen on port 6000, on an IPv4 endpoint, for all incoming IPs.
/// ```
- /// use bevy_quinnet::server::ServerConfigurationData;
- /// let config = ServerConfigurationData::new(
- /// "127.0.0.1".to_string(),
- /// 6000,
- /// "0.0.0.0".to_string(),
- /// );
+ /// use bevy_quinnet::server::ServerConfiguration;
+ /// let config = ServerConfiguration::from_string("0.0.0.0:6000");
/// ```
- pub fn new(host: String, port: u16, local_bind_host: String) -> Self {
+ /// Listen on port 6000, on an IPv6 endpoint, for all incoming IPs.
+ /// ```
+ /// use bevy_quinnet::server::ServerConfiguration;
+ /// let config = ServerConfiguration::from_string("[::]:6000");
+ /// ```
+ pub fn from_string(local_bind_addr_str: &str) -> Result<Self, AddrParseError> {
+ let local_bind_addr = local_bind_addr_str.parse()?;
+ Ok(Self { local_bind_addr })
+ }
+
+ /// Creates a new ServerConfiguration
+ ///
+ /// # Arguments
+ ///
+ /// * `local_bind_ip` - Local IP address to bind to. The address should usually be a wildcard like `0.0.0.0` (for an IPv4) or `0:0:0:0:0:0:0:0` (for an IPv6), which allow communication with any reachable IPv4 or IPv6 address. See [`std::net::Ipv4Addr`] and [`std::net::Ipv6Addr`] for more precision.
+ /// * `local_bind_port` - Local port to bind to.
+ ///
+ /// # Examples
+ ///
+ /// Listen on port 6000, on an IPv4 endpoint, for all incoming IPs.
+ /// ```
+ /// use bevy_quinnet::server::ServerConfiguration;
+ /// let config = ServerConfiguration::from_ip(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6000);
+ /// ```
+ pub fn from_ip(local_bind_ip: IpAddr, local_bind_port: u16) -> Self {
Self {
- host,
- port,
- local_bind_host,
+ local_bind_addr: SocketAddr::new(local_bind_ip, local_bind_port),
}
}
+
+ /// Creates a new ServerConfiguration
+ ///
+ /// # Arguments
+ ///
+ /// * `local_bind_addr` - Local address and port to bind to.
+ /// See [`std::net::SocketAddrV4`] and [`std::net::SocketAddrV6`] for more precision.
+ ///
+ /// # Examples
+ ///
+ /// Listen on port 6000, on an IPv4 endpoint, for all incoming IPs.
+ /// ```
+ /// use bevy_quinnet::server::ServerConfiguration;
+ /// use std::{net::{IpAddr, Ipv4Addr, SocketAddr}};
+ /// let config = ServerConfiguration::from_addr(
+ /// SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 6000),
+ /// );
+ /// ```
+ pub fn from_addr(local_bind_addr: SocketAddr) -> Self {
+ Self { local_bind_addr }
+ }
}
#[derive(Debug)]
@@ -635,19 +661,16 @@ impl Server {
self.endpoint.as_mut()
}
- /// Starts a new endpoint with the given [ServerConfigurationData] and [CertificateRetrievalMode] and opens the default channels.
+ /// Starts a new endpoint with the given [ServerConfiguration] and [CertificateRetrievalMode] and opens the default channels.
///
/// Returns a tuple of the [ServerCertificate] generated or loaded, and the default [ChannelId]
pub fn start_endpoint(
&mut self,
- config: ServerConfigurationData,
+ config: ServerConfiguration,
cert_mode: CertificateRetrievalMode,
) -> Result<(ServerCertificate, ChannelId), QuinnetError> {
- let server_adr_str = format!("{}:{}", config.local_bind_host, config.port);
- let server_addr = server_adr_str.parse::<SocketAddr>()?;
-
// Endpoint configuration
- let server_cert = retrieve_certificate(&config.host, cert_mode)?;
+ let server_cert = retrieve_certificate(cert_mode)?;
let mut server_config = ServerConfig::with_single_cert(
server_cert.cert_chain.clone(),
server_cert.priv_key.clone(),
@@ -661,12 +684,12 @@ impl Server {
let (endpoint_close_send, endpoint_close_recv) =
broadcast::channel(DEFAULT_KILL_MESSAGE_QUEUE_SIZE);
- info!("Starting endpoint on: {} ...", server_adr_str);
+ info!("Starting endpoint on: {} ...", config.local_bind_addr);
self.runtime.spawn(async move {
endpoint_task(
server_config,
- server_addr,
+ config.local_bind_addr,
to_sync_server_send.clone(),
endpoint_close_recv,
)
diff --git a/src/server/certificate.rs b/src/server/certificate.rs
index f13ce16..00bbad3 100644
--- a/src/server/certificate.rs
+++ b/src/server/certificate.rs
@@ -11,8 +11,8 @@ use crate::shared::{CertificateFingerprint, QuinnetError};
/// Represents the origin of a certificate.
#[derive(Debug, Clone)]
pub enum CertOrigin {
- /// Indicates that the certificate was generated. The `server_host` field contains the hostname used when generating the certificate.
- Generated { server_host: String },
+ /// Indicates that the certificate was generated. The `server_hostname` field contains the hostname used when generating the certificate.
+ Generated { server_hostname: String },
/// Indicates that the certificate was loaded from a file.
Loaded,
}
@@ -20,16 +20,17 @@ pub enum CertOrigin {
/// How the server should retrieve its certificate.
#[derive(Debug, Clone)]
pub enum CertificateRetrievalMode {
- /// The server will always generate a new self-signed certificate when starting up
- GenerateSelfSigned,
- /// Try to load cert & key from files.
+ /// The server will always generate a new self-signed certificate when starting up, using `server_hostname` as the subject of the certificate.
+ GenerateSelfSigned { server_hostname: String },
+ /// Try to load cert & key from files `cert_file``and `key_file`.
LoadFromFile { cert_file: String, key_file: String },
- /// Try to load cert & key from files.
- /// If the files do not exist, generate a self-signed certificate, and optionally save it to disk.
+ /// Try to load cert & key from files `cert_file``and `key_file`.
+ /// If the files do not exist, generate a self-signed certificate using `server_hostname` as the subject of the certificate. Optionally save it to disk if `save_on_disk` is enabled.
LoadFromFileOrGenerateSelfSigned {
cert_file: String,
key_file: String,
save_on_disk: bool,
+ server_hostname: String,
},
}
@@ -111,12 +112,11 @@ fn generate_self_signed_certificate(
}
pub(crate) fn retrieve_certificate(
- server_host: &String,
cert_mode: CertificateRetrievalMode,
) -> Result<ServerCertificate, QuinnetError> {
match cert_mode {
- CertificateRetrievalMode::GenerateSelfSigned => {
- let (server_cert, _rcgen_cert) = generate_self_signed_certificate(server_host)?;
+ CertificateRetrievalMode::GenerateSelfSigned { server_hostname } => {
+ let (server_cert, _rcgen_cert) = generate_self_signed_certificate(&server_hostname)?;
trace!("Generatied a new self-signed certificate");
Ok(server_cert)
}
@@ -132,6 +132,7 @@ pub(crate) fn retrieve_certificate(
save_on_disk,
cert_file,
key_file,
+ server_hostname,
} => {
if Path::new(&cert_file).exists() && Path::new(&key_file).exists() {
let server_cert = read_certs_from_files(&cert_file, &key_file)?;
@@ -139,7 +140,7 @@ pub(crate) fn retrieve_certificate(
Ok(server_cert)
} else {
warn!("{} and/or {} do not exist, could not load existing certificate. Generating a new self-signed certificate.", cert_file, key_file);
- let (server_cert, rcgen_cert) = generate_self_signed_certificate(server_host)?;
+ let (server_cert, rcgen_cert) = generate_self_signed_certificate(&server_hostname)?;
if save_on_disk {
write_certs_to_files(&rcgen_cert, &cert_file, &key_file)?;
trace!("Successfuly saved cert and key to files");