From 99304ffddd477e501b40f562e3a1f4169542c180 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 17 May 2021 17:32:26 +0000 Subject: [PATCH 1/2] Revert "Merge branch 'sharp/revert-quinn' into 'master'" This reverts merge request !2315 --- Cargo.lock | 2 + client/Cargo.toml | 3 +- client/src/addr.rs | 187 +++++++++-------- client/src/error.rs | 2 +- client/src/lib.rs | 43 ++-- common/frontend/src/lib.rs | 1 + server/Cargo.toml | 3 +- server/src/lib.rs | 32 +++ server/src/settings.rs | 9 + voxygen/src/menu/char_selection/mod.rs | 4 + voxygen/src/menu/main/client_init.rs | 30 +-- voxygen/src/menu/main/mod.rs | 273 +++++++++++-------------- voxygen/src/settings/networking.rs | 2 + 13 files changed, 296 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd55c47fdb..65bd77291a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5483,6 +5483,7 @@ dependencies = [ "hashbrown", "image", "num 0.4.0", + "quinn", "rayon", "ron", "rustyline", @@ -5752,6 +5753,7 @@ dependencies = [ "portpicker", "prometheus", "prometheus-hyper", + "quinn", "rand 0.8.3", "rand_distr", "rayon", diff --git a/client/Cargo.toml b/client/Cargo.toml index e0113e12f2..02861fba46 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -18,11 +18,12 @@ common-base = { package = "veloren-common-base", path = "../common/base" } common-state = { package = "veloren-common-state", path = "../common/state", default-features = false } common-systems = { package = "veloren-common-systems", path = "../common/systems", default-features = false } common-net = { package = "veloren-common-net", path = "../common/net" } -network = { package = "veloren-network", path = "../network", features = ["compression"], default-features = false } +network = { package = "veloren-network", path = "../network", features = ["compression","quic"], default-features = false } byteorder = "1.3.2" futures-util = "0.3.7" tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } +quinn = "0.7.2" image = { version = "0.23.12", default-features = false, features = ["png"] } num = "0.4" tracing = { version = "0.1", default-features = false } diff --git a/client/src/addr.rs b/client/src/addr.rs index fa8c871aea..539c294006 100644 --- a/client/src/addr.rs +++ b/client/src/addr.rs @@ -4,47 +4,80 @@ use tracing::trace; #[derive(Clone, Debug)] pub enum ConnectionArgs { - IpAndPort(Vec), + ///hostname: (hostname|ip):[] + Quic { + hostname: String, + prefer_ipv6: bool, + }, + ///hostname: (hostname|ip):[] + Tcp { + hostname: String, + prefer_ipv6: bool, + }, Mpsc(u64), } impl ConnectionArgs { const DEFAULT_PORT: u16 = 14004; +} - /// Parse ip address or resolves hostname. - /// Note: If you use an ipv6 address, the number after the last - /// colon will be used as the port unless you use [] around the address. - pub async fn resolve( - /* :[] */ server_address: &str, - prefer_ipv6: bool, - ) -> Result { - // `lookup_host` will internally try to parse it as a SocketAddr - // 1. Assume it's a hostname + port - match lookup_host(server_address).await { - Ok(s) => { - trace!("Host lookup succeeded"); - Ok(Self::sort_ipv6(s, prefer_ipv6)) - }, - Err(e) => { - // 2. Assume its a hostname without port - match lookup_host((server_address, Self::DEFAULT_PORT)).await { - Ok(s) => { - trace!("Host lookup without ports succeeded"); - Ok(Self::sort_ipv6(s, prefer_ipv6)) - }, - Err(_) => Err(e), // Todo: evaluate returning both errors - } +/// Parse ip address or resolves hostname. +/// Note: If you use an ipv6 address, the number after the last +/// colon will be used as the port unless you use [] around the address. +pub(crate) async fn resolve( + address: &str, + prefer_ipv6: bool, +) -> Result, std::io::Error> { + // `lookup_host` will internally try to parse it as a SocketAddr + // 1. Assume it's a hostname + port + match lookup_host(address).await { + Ok(s) => { + trace!("Host lookup succeeded"); + Ok(sort_ipv6(s, prefer_ipv6)) + }, + Err(e) => { + // 2. Assume its a hostname without port + match lookup_host((address, ConnectionArgs::DEFAULT_PORT)).await { + Ok(s) => { + trace!("Host lookup without ports succeeded"); + Ok(sort_ipv6(s, prefer_ipv6)) + }, + Err(_) => Err(e), // Todo: evaluate returning both errors + } + }, + } +} + +pub(crate) async fn try_connect( + network: &network::Network, + address: &str, + prefer_ipv6: bool, + f: F, +) -> Result +where + F: Fn(std::net::SocketAddr) -> network::ConnectAddr, +{ + use crate::error::Error; + let mut participant = None; + for addr in resolve(&address, prefer_ipv6) + .await + .map_err(Error::HostnameLookupFailed)? + { + match network.connect(f(addr)).await { + Ok(p) => { + participant = Some(Ok(p)); + break; }, + Err(e) => participant = Some(Err(Error::NetworkErr(e))), } } + participant.unwrap_or_else(|| Err(Error::Other("No Ip Addr provided".to_string()))) +} - fn sort_ipv6(s: impl Iterator, prefer_ipv6: bool) -> Self { - let (mut first_addrs, mut second_addrs) = - s.partition::, _>(|a| a.is_ipv6() == prefer_ipv6); - let addr = std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..)) - .collect::>(); - ConnectionArgs::IpAndPort(addr) - } +fn sort_ipv6(s: impl Iterator, prefer_ipv6: bool) -> Vec { + let (mut first_addrs, mut second_addrs) = + s.partition::, _>(|a| a.is_ipv6() == prefer_ipv6); + std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..)).collect::>() } #[cfg(test)] @@ -54,85 +87,47 @@ mod tests { #[tokio::test] async fn resolve_localhost() { - let args = ConnectionArgs::resolve("localhost", false) - .await - .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert!(args.len() == 1 || args.len() == 2); - assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); - assert_eq!(args[0].port(), 14004); - } else { - panic!("wrong resolution"); - } + let args = resolve("localhost", false).await.expect("resolve failed"); + assert!(args.len() == 1 || args.len() == 2); + assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); + assert_eq!(args[0].port(), 14004); - let args = ConnectionArgs::resolve("localhost:666", false) + let args = resolve("localhost:666", false) .await .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert!(args.len() == 1 || args.len() == 2); - assert_eq!(args[0].port(), 666); - } else { - panic!("wrong resolution"); - } + assert!(args.len() == 1 || args.len() == 2); + assert_eq!(args[0].port(), 666); } #[tokio::test] async fn resolve_ipv6() { - let args = ConnectionArgs::resolve("localhost", true) - .await - .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert!(args.len() == 1 || args.len() == 2); - assert_eq!(args[0].ip(), Ipv6Addr::LOCALHOST); - assert_eq!(args[0].port(), 14004); - } else { - panic!("wrong resolution"); - } + let args = resolve("localhost", true).await.expect("resolve failed"); + assert!(args.len() == 1 || args.len() == 2); + assert_eq!(args[0].ip(), Ipv6Addr::LOCALHOST); + assert_eq!(args[0].port(), 14004); } #[tokio::test] - async fn resolve() { - let args = ConnectionArgs::resolve("google.com", false) - .await - .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert!(args.len() == 1 || args.len() == 2); - assert_eq!(args[0].port(), 14004); - } else { - panic!("wrong resolution"); - } + async fn tresolve() { + let args = resolve("google.com", false).await.expect("resolve failed"); + assert!(args.len() == 1 || args.len() == 2); + assert_eq!(args[0].port(), 14004); - let args = ConnectionArgs::resolve("127.0.0.1", false) - .await - .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert_eq!(args.len(), 1); - assert_eq!(args[0].port(), 14004); - assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); - } else { - panic!("wrong resolution"); - } + let args = resolve("127.0.0.1", false).await.expect("resolve failed"); + assert_eq!(args.len(), 1); + assert_eq!(args[0].port(), 14004); + assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); - let args = ConnectionArgs::resolve("55.66.77.88", false) - .await - .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert_eq!(args.len(), 1); - assert_eq!(args[0].port(), 14004); - assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::new(55, 66, 77, 88))); - } else { - panic!("wrong resolution"); - } + let args = resolve("55.66.77.88", false).await.expect("resolve failed"); + assert_eq!(args.len(), 1); + assert_eq!(args[0].port(), 14004); + assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::new(55, 66, 77, 88))); - let args = ConnectionArgs::resolve("127.0.0.1:776", false) + let args = resolve("127.0.0.1:776", false) .await .expect("resolve failed"); - if let ConnectionArgs::IpAndPort(args) = args { - assert_eq!(args.len(), 1); - assert_eq!(args[0].port(), 776); - assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); - } else { - panic!("wrong resolution"); - } + assert_eq!(args.len(), 1); + assert_eq!(args[0].port(), 776); + assert_eq!(args[0].ip(), IpAddr::V4(Ipv4Addr::LOCALHOST)); } } diff --git a/client/src/error.rs b/client/src/error.rs index a9079d77d6..adceaddf04 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -9,7 +9,6 @@ pub enum Error { NetworkErr(NetworkError), ParticipantErr(ParticipantError), StreamErr(StreamError), - ServerWentMad, ServerTimeout, ServerShutdown, TooManyPlayers, @@ -18,6 +17,7 @@ pub enum Error { AuthClientError(AuthClientError), AuthServerUrlInvalid(String), AuthServerNotTrusted, + HostnameLookupFailed(std::io::Error), Banned(String), /// Persisted character data is invalid or missing InvalidCharacter, diff --git a/client/src/lib.rs b/client/src/lib.rs index df73a2a780..064d0b3a1d 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -203,10 +203,8 @@ pub struct CharacterList { } impl Client { - /// Create a new `Client`. pub async fn new( addr: ConnectionArgs, - view_distance: Option, runtime: Arc, // TODO: refactor to avoid needing to use this out parameter mismatched_server_info: &mut Option, @@ -214,20 +212,21 @@ impl Client { let network = Network::new(Pid::new(), &runtime); let participant = match addr { - ConnectionArgs::IpAndPort(addrs) => { - // Try to connect to all IP's and return the first that works - let mut participant = None; - for addr in addrs { - match network.connect(ConnectAddr::Tcp(addr)).await { - Ok(p) => { - participant = Some(Ok(p)); - break; - }, - Err(e) => participant = Some(Err(Error::NetworkErr(e))), - } - } - participant - .unwrap_or_else(|| Err(Error::Other("No Ip Addr provided".to_string())))? + ConnectionArgs::Tcp { + hostname, + prefer_ipv6, + } => addr::try_connect(&network, &hostname, prefer_ipv6, ConnectAddr::Tcp).await?, + ConnectionArgs::Quic { + hostname, + prefer_ipv6, + } => { + let mut config = quinn::ClientConfigBuilder::default(); + config.protocols(&[b"VELOREN"]); + let config = config.build(); + addr::try_connect(&network, &hostname, prefer_ipv6, |a| { + ConnectAddr::Quic(a, config.clone(), hostname.clone()) + }) + .await? }, ConnectionArgs::Mpsc(id) => network.connect(ConnectAddr::Mpsc(id)).await?, }; @@ -694,7 +693,7 @@ impl Client { tick: 0, state, - view_distance, + view_distance: None, loaded_distance: 0.0, pending_chunks: HashMap::new(), @@ -2447,7 +2446,6 @@ impl Drop for Client { #[cfg(test)] mod tests { use super::*; - use std::net::SocketAddr; #[test] /// THIS TEST VERIFIES THE CONSTANT API. @@ -2457,17 +2455,16 @@ mod tests { /// CONTACT @Core Developer BEFORE MERGING CHANGES TO THIS TEST fn constant_api_test() { use common::clock::Clock; - use std::net::{IpAddr, Ipv4Addr}; const SPT: f64 = 1.0 / 60.0; - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9000); - let view_distance: Option = None; let runtime = Arc::new(Runtime::new().unwrap()); let runtime2 = Arc::clone(&runtime); let veloren_client: Result = runtime.block_on(Client::new( - ConnectionArgs::IpAndPort(vec![socket]), - view_distance, + ConnectionArgs::Tcp { + hostname: "127.0.0.1:9000".to_owned(), + prefer_ipv6: false, + }, runtime2, &mut None, )); diff --git a/common/frontend/src/lib.rs b/common/frontend/src/lib.rs index 522c34a118..40278de603 100644 --- a/common/frontend/src/lib.rs +++ b/common/frontend/src/lib.rs @@ -59,6 +59,7 @@ where .add_directive("tokio_util=info".parse().unwrap()) .add_directive("rustls=info".parse().unwrap()) .add_directive("veloren_network_protocol=info".parse().unwrap()) + .add_directive("quinn_proto::connection=info".parse().unwrap()) .add_directive( "veloren_server::persistence::character=info" .parse() diff --git a/server/Cargo.toml b/server/Cargo.toml index e6c61d8516..4f24502bd5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,7 +20,7 @@ common-state = { package = "veloren-common-state", path = "../common/state" } common-systems = { package = "veloren-common-systems", path = "../common/systems" } common-net = { package = "veloren-common-net", path = "../common/net" } world = { package = "veloren-world", path = "../world" } -network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false } +network = { package = "veloren-network", path = "../network", features = ["metrics", "compression", "quic"], default-features = false } # inline_tweak = "1.0.8" @@ -33,6 +33,7 @@ vek = { version = "0.14.1", features = ["serde"] } futures-util = "0.3.7" tokio = { version = "1", default-features = false, features = ["rt"] } prometheus-hyper = "0.1.2" +quinn = "0.7.2" atomicwrites = "0.3.0" chrono = { version = "0.4.9", features = ["serde"] } humantime = "2.1.0" diff --git a/server/src/lib.rs b/server/src/lib.rs index 705f5178b5..84883e4d74 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -398,6 +398,38 @@ impl Server { }); runtime.block_on(network.listen(ListenAddr::Tcp(settings.gameserver_address)))?; runtime.block_on(network.listen(ListenAddr::Mpsc(14004)))?; + if let Some(quic) = &settings.quic_files { + use std::fs; + match || -> Result<_, Box> { + let mut server_config = + quinn::ServerConfigBuilder::new(quinn::ServerConfig::default()); + server_config.protocols(&[b"VELOREN"]); + let key = fs::read(&quic.key)?; + let key = if quic.key.extension().map_or(false, |x| x == "der") { + quinn::PrivateKey::from_der(&key)? + } else { + quinn::PrivateKey::from_pem(&key)? + }; + let cert_chain = fs::read(&quic.cert)?; + let cert_chain = if quic.cert.extension().map_or(false, |x| x == "der") { + quinn::CertificateChain::from_certs(Some( + quinn::Certificate::from_der(&cert_chain).unwrap(), + )) + } else { + quinn::CertificateChain::from_pem(&cert_chain)? + }; + server_config.certificate(cert_chain, key)?; + Ok(server_config.build()) + }() { + Ok(server_config) => { + runtime.block_on( + network + .listen(ListenAddr::Quic(settings.gameserver_address, server_config)), + )?; + }, + Err(e) => error!(?e, "Failed to load Quic Certificate, run without Quic"), + } + } let connection_handler = ConnectionHandler::new(network, &runtime); // Initiate real-time world simulation diff --git a/server/src/settings.rs b/server/src/settings.rs index 1424af9fe5..5c819381d1 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -33,12 +33,19 @@ const BANLIST_FILENAME: &str = "banlist.ron"; const SERVER_DESCRIPTION_FILENAME: &str = "description.ron"; const ADMINS_FILENAME: &str = "admins.ron"; +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct X509FilePair { + pub cert: PathBuf, + pub key: PathBuf, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { pub gameserver_address: SocketAddr, pub metrics_address: SocketAddr, pub auth_server_address: Option, + pub quic_files: Option, pub max_players: usize, pub world_seed: u32, //pub pvp_enabled: bool, @@ -62,6 +69,7 @@ impl Default for Settings { gameserver_address: SocketAddr::from(([0; 4], 14004)), metrics_address: SocketAddr::from(([0; 4], 14005)), auth_server_address: Some("https://auth.veloren.net".into()), + quic_files: None, world_seed: DEFAULT_WORLD_SEED, server_name: "Veloren Alpha".into(), max_players: 100, @@ -140,6 +148,7 @@ impl Settings { pick_unused_port().expect("Failed to find unused port!"), )), auth_server_address: None, + quic_files: None, // If loading the default map file, make sure the seed is also default. world_seed: if load.map_file.is_some() { load.world_seed diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index a7825976a7..007914cb4d 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -116,6 +116,10 @@ impl PlayState for CharSelectionState { }, ui::Event::Play(character_id) => { self.client.borrow_mut().request_character(character_id); + //Send our ViewDistance + self.client + .borrow_mut() + .set_view_distance(global_state.settings.graphics.view_distance); return PlayStateResult::Switch(Box::new(SessionState::new( global_state, diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index de69abc5a5..622633660c 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -17,7 +17,6 @@ use tracing::{trace, warn}; #[derive(Debug)] pub enum Error { - NoAddress, ClientError { error: ClientError, mismatched_server_info: Option, @@ -31,12 +30,6 @@ pub enum Msg { Done(Result), } -pub enum ClientConnArgs { - Host(String), - #[allow(dead_code)] //singleplayer - Resolved(ConnectionArgs), -} - pub struct AuthTrust(String, bool); // Used to asynchronously parse the server address, resolve host names, @@ -51,9 +44,8 @@ impl ClientInit { #[allow(clippy::op_ref)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 pub fn new( - connection_args: ClientConnArgs, + connection_args: ConnectionArgs, username: String, - view_distance: Option, password: String, runtime: Option>, ) -> Self { @@ -89,18 +81,6 @@ impl ClientInit { .unwrap_or(false) }; - let connection_args = match connection_args { - ClientConnArgs::Host(host) => match ConnectionArgs::resolve(&host, false).await { - Ok(r) => r, - Err(_) => { - let _ = tx.send(Msg::Done(Err(Error::NoAddress))); - tokio::task::block_in_place(move || drop(runtime2)); - return; - }, - }, - ClientConnArgs::Resolved(r) => r, - }; - let mut last_err = None; const FOUR_MINUTES_RETRIES: u64 = 48; @@ -111,7 +91,6 @@ impl ClientInit { let mut mismatched_server_info = None; match Client::new( connection_args.clone(), - view_distance, Arc::clone(&runtime2), &mut mismatched_server_info, ) @@ -146,8 +125,11 @@ impl ClientInit { tokio::time::sleep(Duration::from_secs(5)).await; } - // Parsing/host name resolution successful but no connection succeeded. - let _ = tx.send(Msg::Done(Err(last_err.unwrap_or(Error::NoAddress)))); + // Only possibility for no last_err is aborting + let _ = tx.send(Msg::Done(Err(last_err.unwrap_or(Error::ClientError { + error: ClientError::Other("Connection attempt aborted by user".to_owned()), + mismatched_server_info: None, + })))); // Safe drop runtime tokio::task::block_in_place(move || drop(runtime2)); diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index dac729c725..d03c45167b 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -5,22 +5,18 @@ use super::char_selection::CharSelectionState; #[cfg(feature = "singleplayer")] use crate::singleplayer::Singleplayer; use crate::{ - i18n::{Localization, LocalizationHandle}, - render::Renderer, - settings::Settings, - window::Event, - Direction, GlobalState, PlayState, PlayStateResult, + i18n::LocalizationHandle, render::Renderer, settings::Settings, window::Event, Direction, + GlobalState, PlayState, PlayStateResult, }; -#[cfg(feature = "singleplayer")] -use client::addr::ConnectionArgs; use client::{ + addr::ConnectionArgs, error::{InitProtocolError, NetworkConnectError, NetworkError}, ServerInfo, }; -use client_init::{ClientConnArgs, ClientInit, Error as InitError, Msg as InitMsg}; +use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::comp; use common_base::span; -use std::{fmt::Debug, sync::Arc}; +use std::sync::Arc; use tokio::runtime; use tracing::error; use ui::{Event as MainMenuEvent, MainMenuUi}; @@ -74,11 +70,10 @@ impl PlayState for MainMenuState { Ok(Ok(runtime)) => { // Attempt login after the server is finished initializing attempt_login( - &mut global_state.settings, &mut global_state.info_message, "singleplayer".to_owned(), "".to_owned(), - ClientConnArgs::Resolved(ConnectionArgs::Mpsc(14004)), + ConnectionArgs::Mpsc(14004), &mut self.client_init, Some(runtime), ); @@ -120,117 +115,14 @@ impl PlayState for MainMenuState { std::rc::Rc::new(std::cell::RefCell::new(client)), ))); }, - Some(InitMsg::Done(Err(err))) => { - let localized_strings = global_state.i18n.read(); + Some(InitMsg::Done(Err(e))) => { self.client_init = None; - global_state.info_message = Some({ - let err = match err { - InitError::NoAddress => { - localized_strings.get("main.login.server_not_found").into() - }, - InitError::ClientError { - error, - mismatched_server_info, - } => match error { - client::Error::SpecsErr(e) => format!( - "{}: {}", - localized_strings.get("main.login.internal_error"), - e - ), - client::Error::AuthErr(e) => format!( - "{}: {}", - localized_strings.get("main.login.authentication_error"), - e - ), - client::Error::Kicked(e) => e, - client::Error::TooManyPlayers => { - localized_strings.get("main.login.server_full").into() - }, - client::Error::AuthServerNotTrusted => localized_strings - .get("main.login.untrusted_auth_server") - .into(), - client::Error::ServerWentMad => localized_strings - .get("main.login.outdated_client_or_server") - .into(), - client::Error::ServerTimeout => { - localized_strings.get("main.login.timeout").into() - }, - client::Error::ServerShutdown => { - localized_strings.get("main.login.server_shut_down").into() - }, - client::Error::NotOnWhitelist => { - localized_strings.get("main.login.not_on_whitelist").into() - }, - client::Error::Banned(reason) => format!( - "{}: {}", - localized_strings.get("main.login.banned"), - reason - ), - client::Error::InvalidCharacter => { - localized_strings.get("main.login.invalid_character").into() - }, - client::Error::NetworkErr(NetworkError::ConnectFailed( - NetworkConnectError::Handshake(InitProtocolError::WrongVersion(_)), - )) => get_network_error_text( - &localized_strings, - localized_strings.get("main.login.network_wrong_version"), - mismatched_server_info, - ), - client::Error::NetworkErr(e) => get_network_error_text( - &localized_strings, - e, - mismatched_server_info, - ), - client::Error::ParticipantErr(e) => get_network_error_text( - &localized_strings, - e, - mismatched_server_info, - ), - client::Error::StreamErr(e) => get_network_error_text( - &localized_strings, - e, - mismatched_server_info, - ), - client::Error::Other(e) => { - format!("{}: {}", localized_strings.get("common.error"), e) - }, - client::Error::AuthClientError(e) => match e { - // TODO: remove parentheses - client::AuthClientError::RequestError(e) => format!( - "{}: {}", - localized_strings.get("main.login.failed_sending_request"), - e - ), - client::AuthClientError::JsonError(e) => format!( - "{}: {}", - localized_strings.get("main.login.failed_sending_request"), - e - ), - client::AuthClientError::InsecureSchema => localized_strings - .get("main.login.insecure_auth_scheme") - .into(), - client::AuthClientError::ServerError(_, e) => { - String::from_utf8_lossy(&e).to_string() - }, - }, - client::Error::AuthServerUrlInvalid(e) => { - format!( - "{}: https://{}", - localized_strings - .get("main.login.failed_auth_server_url_invalid"), - e - ) - }, - }, - InitError::ClientCrashed => { - localized_strings.get("main.login.client_crashed").into() - }, - }; - // Log error for possible additional use later or incase that the error - // displayed is cut of. - error!("{}", err); - err - }); + tracing::trace!(?e, "raw Client Init error"); + let e = get_client_msg_error(e, &global_state.i18n); + // Log error for possible additional use later or incase that the error + // displayed is cut of. + error!(?e, "Client Init failed"); + global_state.info_message = Some(e); }, Some(InitMsg::IsAuthTrusted(auth_server)) => { if global_state @@ -264,6 +156,7 @@ impl PlayState for MainMenuState { server_address, } => { let mut net_settings = &mut global_state.settings.networking; + let use_quic = net_settings.use_quic; net_settings.username = username.clone(); net_settings.default_server = server_address.clone(); if !net_settings.servers.contains(&server_address) { @@ -271,12 +164,22 @@ impl PlayState for MainMenuState { } global_state.settings.save_to_file_warn(); + let connection_args = if use_quic { + ConnectionArgs::Quic { + hostname: server_address, + prefer_ipv6: false, + } + } else { + ConnectionArgs::Tcp { + hostname: server_address, + prefer_ipv6: false, + } + }; attempt_login( - &mut global_state.settings, &mut global_state.info_message, username, password, - ClientConnArgs::Host(server_address), + connection_args, &mut self.client_init, None, ); @@ -347,37 +250,110 @@ impl PlayState for MainMenuState { } } -/// When a network error is received and there is a mismatch between the client -/// and server version it is almost definitely due to this mismatch rather than -/// a true networking error. -fn get_network_error_text( - localization: &Localization, - error: impl Debug, - mismatched_server_info: Option, -) -> String { - if let Some(server_info) = mismatched_server_info { - format!( - "{} {}: {} {}: {}", - localization.get("main.login.network_wrong_version"), - localization.get("main.login.client_version"), - common::util::GIT_HASH.to_string(), - localization.get("main.login.server_version"), - server_info.git_hash - ) - } else { - format!( - "{}: {:?}", - localization.get("main.login.network_error"), - error - ) +fn get_client_msg_error(e: client_init::Error, localized_strings: &LocalizationHandle) -> String { + let localization = localized_strings.read(); + + // When a network error is received and there is a mismatch between the client + // and server version it is almost definitely due to this mismatch rather than + // a true networking error. + let net_e = |error: String, mismatched_server_info: Option| -> String { + if let Some(server_info) = mismatched_server_info { + format!( + "{} {}: {} {}: {}", + localization.get("main.login.network_wrong_version"), + localization.get("main.login.client_version"), + common::util::GIT_HASH.to_string(), + localization.get("main.login.server_version"), + server_info.git_hash + ) + } else { + format!( + "{}: {}", + localization.get("main.login.network_error"), + error + ) + } + }; + + use client::Error; + match e { + InitError::ClientError { + error, + mismatched_server_info, + } => match error { + Error::SpecsErr(e) => { + format!("{}: {}", localization.get("main.login.internal_error"), e) + }, + Error::AuthErr(e) => format!( + "{}: {}", + localization.get("main.login.authentication_error"), + e + ), + Error::Kicked(e) => e, + Error::TooManyPlayers => localization.get("main.login.server_full").into(), + Error::AuthServerNotTrusted => { + localization.get("main.login.untrusted_auth_server").into() + }, + Error::ServerTimeout => localization.get("main.login.timeout").into(), + Error::ServerShutdown => localization.get("main.login.server_shut_down").into(), + Error::NotOnWhitelist => localization.get("main.login.not_on_whitelist").into(), + Error::Banned(reason) => { + format!("{}: {}", localization.get("main.login.banned"), reason) + }, + Error::InvalidCharacter => localization.get("main.login.invalid_character").into(), + Error::NetworkErr(NetworkError::ConnectFailed(NetworkConnectError::Handshake( + InitProtocolError::WrongVersion(_), + ))) => net_e( + localization + .get("main.login.network_wrong_version") + .to_owned(), + mismatched_server_info, + ), + Error::NetworkErr(e) => net_e(e.to_string(), mismatched_server_info), + Error::ParticipantErr(e) => net_e(e.to_string(), mismatched_server_info), + Error::StreamErr(e) => net_e(e.to_string(), mismatched_server_info), + Error::HostnameLookupFailed(e) => { + format!("{}: {}", localization.get("main.login.server_not_found"), e) + }, + Error::Other(e) => { + format!("{}: {}", localization.get("common.error"), e) + }, + Error::AuthClientError(e) => match e { + // TODO: remove parentheses + client::AuthClientError::RequestError(e) => format!( + "{}: {}", + localization.get("main.login.failed_sending_request"), + e + ), + client::AuthClientError::JsonError(e) => format!( + "{}: {}", + localization.get("main.login.failed_sending_request"), + e + ), + client::AuthClientError::InsecureSchema => { + localization.get("main.login.insecure_auth_scheme").into() + }, + client::AuthClientError::ServerError(_, e) => { + String::from_utf8_lossy(&e).to_string() + }, + }, + Error::AuthServerUrlInvalid(e) => { + format!( + "{}: https://{}", + localization.get("main.login.failed_auth_server_url_invalid"), + e + ) + }, + }, + InitError::ClientCrashed => localization.get("main.login.client_crashed").into(), } } + fn attempt_login( - settings: &mut Settings, info_message: &mut Option, username: String, password: String, - connection_args: ClientConnArgs, + connection_args: ConnectionArgs, client_init: &mut Option, runtime: Option>, ) { @@ -391,7 +367,6 @@ fn attempt_login( *client_init = Some(ClientInit::new( connection_args, username, - Some(settings.graphics.view_distance), password, runtime, )); diff --git a/voxygen/src/settings/networking.rs b/voxygen/src/settings/networking.rs index 4732b5dc7e..402fb5f4e4 100644 --- a/voxygen/src/settings/networking.rs +++ b/voxygen/src/settings/networking.rs @@ -9,6 +9,7 @@ pub struct NetworkingSettings { pub servers: Vec, pub default_server: String, pub trusted_auth_servers: HashSet, + pub use_quic: bool, } impl Default for NetworkingSettings { @@ -21,6 +22,7 @@ impl Default for NetworkingSettings { .iter() .map(|s| s.to_string()) .collect(), + use_quic: false, } } } From cf3188b41246cf1193d3c434fcd751df96ac214f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Thu, 20 May 2021 01:59:30 +0200 Subject: [PATCH 2/2] remove Protocol from Quic, cleanup code, fix some log spam --- client/src/lib.rs | 8 ++++-- network/src/channel.rs | 2 +- network/src/participant.rs | 38 +++++++++++++++----------- network/tests/helper.rs | 2 -- server/src/lib.rs | 8 ++++-- voxygen/src/menu/char_selection/mod.rs | 12 ++++---- 6 files changed, 40 insertions(+), 30 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 064d0b3a1d..d8b7b93856 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -220,9 +220,11 @@ impl Client { hostname, prefer_ipv6, } => { - let mut config = quinn::ClientConfigBuilder::default(); - config.protocols(&[b"VELOREN"]); - let config = config.build(); + warn!( + "QUIC is enabled. This is experimental and you won't be able to connect to \ + TCP servers unless deactivated" + ); + let config = quinn::ClientConfigBuilder::default().build(); addr::try_connect(&network, &hostname, prefer_ipv6, |a| { ConnectAddr::Quic(a, config.clone(), hostname.clone()) }) diff --git a/network/src/channel.rs b/network/src/channel.rs index aa5805800b..6395212e8d 100644 --- a/network/src/channel.rs +++ b/network/src/channel.rs @@ -548,7 +548,7 @@ impl UnreliableDrain for QuicDrain { QuicDataFormatStream::Unreliable => unimplemented!(), QuicDataFormatStream::Reliable(sid) => { use hashbrown::hash_map::Entry; - tracing::trace!(?sid, "Reliable"); + //tracing::trace!(?sid, "Reliable"); match self.reliables.entry(sid) { Entry::Occupied(mut occupied) => occupied.get_mut().write_all(&data.data).await, Entry::Vacant(vacant) => { diff --git a/network/src/participant.rs b/network/src/participant.rs index 3d669b6209..3ed8a49c4a 100644 --- a/network/src/participant.rs +++ b/network/src/participant.rs @@ -193,24 +193,30 @@ impl BParticipant { fn best_protocol(all: &SortedVec, promises: Promises) -> Option { // check for mpsc - for (cid, p) in all.data.iter() { - if matches!(p, SendProtocols::Mpsc(_)) { - return Some(*cid); + all.data.iter().find(|(_, p)| matches!(p, SendProtocols::Mpsc(_))).map(|(c, _)| *c).or_else( + || if network_protocol::TcpSendProtocol::::supported_promises() + .contains(promises) + { + // check for tcp + all.data.iter().find(|(_, p)| matches!(p, SendProtocols::Tcp(_))).map(|(c, _)| *c) + } else { + None } - } - // check for tcp - if network_protocol::TcpSendProtocol::::supported_promises() - .contains(promises) - { - for (cid, p) in all.data.iter() { - if matches!(p, SendProtocols::Tcp(_)) { - return Some(*cid); - } + ).or_else( + // check for quic, TODO: evaluate to order quic BEFORE tcp once its stable + || if network_protocol::QuicSendProtocol::::supported_promises() + .contains(promises) + { + all.data.iter().find(|(_, p)| matches!(p, SendProtocols::Quic(_))).map(|(c, _)| *c) + } else { + None } - } - - warn!("couldn't satisfy promises"); - all.data.first().map(|(c, _)| *c) + ).or_else( + || { + warn!("couldn't satisfy promises"); + all.data.first().map(|(c, _)| *c) + } + ) } //TODO: local stream_cid: HashMap to know the respective protocol diff --git a/network/tests/helper.rs b/network/tests/helper.rs index 9e78928f55..45abf32551 100644 --- a/network/tests/helper.rs +++ b/network/tests/helper.rs @@ -99,7 +99,6 @@ pub fn quic() -> (ListenAddr, ConnectAddr) { let mut server_config = quinn::ServerConfig::default(); server_config.transport = Arc::new(transport_config); let mut server_config = quinn::ServerConfigBuilder::new(server_config); - server_config.protocols(&[b"veloren"]); trace!("generating self-signed certificate"); let cert = rcgen::generate_simple_self_signed(vec![LOCALHOST.into()]).unwrap(); @@ -115,7 +114,6 @@ pub fn quic() -> (ListenAddr, ConnectAddr) { let server_config = server_config.build(); let mut client_config = quinn::ClientConfigBuilder::default(); - client_config.protocols(&[b"veloren"]); client_config .add_certificate_authority(cert) .expect("adding certificate failed"); diff --git a/server/src/lib.rs b/server/src/lib.rs index 84883e4d74..bc828d7b15 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -403,7 +403,6 @@ impl Server { match || -> Result<_, Box> { let mut server_config = quinn::ServerConfigBuilder::new(quinn::ServerConfig::default()); - server_config.protocols(&[b"VELOREN"]); let key = fs::read(&quic.key)?; let key = if quic.key.extension().map_or(false, |x| x == "der") { quinn::PrivateKey::from_der(&key)? @@ -422,12 +421,17 @@ impl Server { Ok(server_config.build()) }() { Ok(server_config) => { + warn!( + "QUIC is enabled. This is experimental and not recommended in production" + ); runtime.block_on( network .listen(ListenAddr::Quic(settings.gameserver_address, server_config)), )?; }, - Err(e) => error!(?e, "Failed to load Quic Certificate, run without Quic"), + Err(e) => { + error!(?e, ?settings.quic_files, "Failed to load Quic Certificate, run without Quic") + }, } } let connection_handler = ConnectionHandler::new(network, &runtime); diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 007914cb4d..ae3f7c0a32 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -115,12 +115,12 @@ impl PlayState for CharSelectionState { self.client.borrow_mut().delete_character(character_id); }, ui::Event::Play(character_id) => { - self.client.borrow_mut().request_character(character_id); - //Send our ViewDistance - self.client - .borrow_mut() - .set_view_distance(global_state.settings.graphics.view_distance); - + { + let mut c = self.client.borrow_mut(); + c.request_character(character_id); + //Send our ViewDistance + c.set_view_distance(global_state.settings.graphics.view_distance); + } return PlayStateResult::Switch(Box::new(SessionState::new( global_state, Rc::clone(&self.client),