diff options
author | gilles henaux <gill.henaux@gmail.com> | 2023-02-20 21:47:41 +0100 |
---|---|---|
committer | gilles henaux <gill.henaux@gmail.com> | 2023-02-20 21:47:41 +0100 |
commit | 6d85439ea69cecb9635a4eee64b4d784312ec3f5 (patch) | |
tree | 505d623d308170346ed31bfeae86a181d6a99229 | |
parent | 81527b24060ab73f5bdaad252a2d62fd42f36dd7 (diff) |
[client & server] Hanlde IPv6 addresses properly: rework ConnectionConfiguration and ServerConfiguration
-rw-r--r-- | src/client/connection.rs | 164 | ||||
-rw-r--r-- | src/server.rs | 101 | ||||
-rw-r--r-- | src/server/certificate.rs | 23 |
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"); |