Add Quin support, as this is not yet very much tested it needs to be activated in the settings of SERVER and CLIENT.

Server:
 provide a certificate file and key file via the settings. When provided it will then listen on TCP and QUIC, if not provided it will be TCP only.
 The certificate must be known by the client, so you might get problems with self-signed certificates.
 ```ron
 quic_files: Some((
     cert: "/home/user/veloren_cert.pem",
     key: "/home/user/veloren_key.key",
 )),
 ```

Client:
 activate the voxygen settin `use_quic: true` to try to connect to the quic backend of a server.
This commit is contained in:
Marcel Märtens 2021-05-07 13:46:00 +02:00
parent 0cf0f59fa7
commit b443e4dd31
11 changed files with 260 additions and 212 deletions

2
Cargo.lock generated
View File

@ -5477,6 +5477,7 @@ dependencies = [
"hashbrown", "hashbrown",
"image", "image",
"num 0.4.0", "num 0.4.0",
"quinn",
"rayon", "rayon",
"ron", "ron",
"rustyline", "rustyline",
@ -5745,6 +5746,7 @@ dependencies = [
"portpicker", "portpicker",
"prometheus", "prometheus",
"prometheus-hyper", "prometheus-hyper",
"quinn",
"rand 0.8.3", "rand 0.8.3",
"rand_distr", "rand_distr",
"rayon", "rayon",

View File

@ -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-state = { package = "veloren-common-state", path = "../common/state", default-features = false }
common-systems = { package = "veloren-common-systems", path = "../common/systems", default-features = false } common-systems = { package = "veloren-common-systems", path = "../common/systems", default-features = false }
common-net = { package = "veloren-common-net", path = "../common/net" } 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" byteorder = "1.3.2"
futures-util = "0.3.7" futures-util = "0.3.7"
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
quinn = "0.7.2"
image = { version = "0.23.12", default-features = false, features = ["png"] } image = { version = "0.23.12", default-features = false, features = ["png"] }
num = "0.4" num = "0.4"
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }

View File

@ -4,33 +4,43 @@ use tracing::trace;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ConnectionArgs { pub enum ConnectionArgs {
IpAndPort(Vec<SocketAddr>), ///hostname: (hostname|ip):[<port>]
Quic {
hostname: String,
prefer_ipv6: bool,
},
///hostname: (hostname|ip):[<port>]
Tcp {
hostname: String,
prefer_ipv6: bool,
},
Mpsc(u64), Mpsc(u64),
} }
impl ConnectionArgs { impl ConnectionArgs {
const DEFAULT_PORT: u16 = 14004; const DEFAULT_PORT: u16 = 14004;
}
/// Parse ip address or resolves hostname. /// Parse ip address or resolves hostname.
/// Note: If you use an ipv6 address, the number after the last /// 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. /// colon will be used as the port unless you use [] around the address.
pub async fn resolve( pub(crate) async fn resolve(
/* <hostname/ip>:[<port>] */ server_address: &str, address: &str,
prefer_ipv6: bool, prefer_ipv6: bool,
) -> Result<Self, std::io::Error> { ) -> Result<Vec<SocketAddr>, std::io::Error> {
// `lookup_host` will internally try to parse it as a SocketAddr // `lookup_host` will internally try to parse it as a SocketAddr
// 1. Assume it's a hostname + port // 1. Assume it's a hostname + port
match lookup_host(server_address).await { match lookup_host(address).await {
Ok(s) => { Ok(s) => {
trace!("Host lookup succeeded"); trace!("Host lookup succeeded");
Ok(Self::sort_ipv6(s, prefer_ipv6)) Ok(sort_ipv6(s, prefer_ipv6))
}, },
Err(e) => { Err(e) => {
// 2. Assume its a hostname without port // 2. Assume its a hostname without port
match lookup_host((server_address, Self::DEFAULT_PORT)).await { match lookup_host((address, ConnectionArgs::DEFAULT_PORT)).await {
Ok(s) => { Ok(s) => {
trace!("Host lookup without ports succeeded"); trace!("Host lookup without ports succeeded");
Ok(Self::sort_ipv6(s, prefer_ipv6)) Ok(sort_ipv6(s, prefer_ipv6))
}, },
Err(_) => Err(e), // Todo: evaluate returning both errors Err(_) => Err(e), // Todo: evaluate returning both errors
} }
@ -38,13 +48,36 @@ impl ConnectionArgs {
} }
} }
fn sort_ipv6(s: impl Iterator<Item = SocketAddr>, prefer_ipv6: bool) -> Self { pub(crate) async fn try_connect<F>(
network: &network::Network,
address: &str,
prefer_ipv6: bool,
f: F,
) -> Result<network::Participant, crate::error::Error>
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(|e| Error::HostnameLookupFailed(e))?
{
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<Item = SocketAddr>, prefer_ipv6: bool) -> Vec<SocketAddr> {
let (mut first_addrs, mut second_addrs) = let (mut first_addrs, mut second_addrs) =
s.partition::<Vec<_>, _>(|a| a.is_ipv6() == prefer_ipv6); s.partition::<Vec<_>, _>(|a| a.is_ipv6() == prefer_ipv6);
let addr = std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..)) std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..)).collect::<Vec<_>>()
.collect::<Vec<_>>();
ConnectionArgs::IpAndPort(addr)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -18,6 +18,7 @@ pub enum Error {
AuthClientError(AuthClientError), AuthClientError(AuthClientError),
AuthServerUrlInvalid(String), AuthServerUrlInvalid(String),
AuthServerNotTrusted, AuthServerNotTrusted,
HostnameLookupFailed(std::io::Error),
Banned(String), Banned(String),
/// Persisted character data is invalid or missing /// Persisted character data is invalid or missing
InvalidCharacter, InvalidCharacter,

View File

@ -203,7 +203,6 @@ pub struct CharacterList {
} }
impl Client { impl Client {
/// Create a new `Client`.
pub async fn new( pub async fn new(
addr: ConnectionArgs, addr: ConnectionArgs,
view_distance: Option<u32>, view_distance: Option<u32>,
@ -214,20 +213,23 @@ impl Client {
let network = Network::new(Pid::new(), &runtime); let network = Network::new(Pid::new(), &runtime);
let participant = match addr { let participant = match addr {
ConnectionArgs::IpAndPort(addrs) => { ConnectionArgs::Tcp {
// Try to connect to all IP's and return the first that works hostname,
let mut participant = None; prefer_ipv6,
for addr in addrs { } => {
match network.connect(ConnectAddr::Tcp(addr)).await { addr::try_connect(&network, &hostname, prefer_ipv6, |a| ConnectAddr::Tcp(a)).await?
Ok(p) => {
participant = Some(Ok(p));
break;
}, },
Err(e) => participant = Some(Err(Error::NetworkErr(e))), ConnectionArgs::Quic {
} hostname,
} prefer_ipv6,
participant } => {
.unwrap_or_else(|| Err(Error::Other("No Ip Addr provided".to_string())))? 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?, ConnectionArgs::Mpsc(id) => network.connect(ConnectAddr::Mpsc(id)).await?,
}; };

View File

@ -20,7 +20,7 @@ common-state = { package = "veloren-common-state", path = "../common/state" }
common-systems = { package = "veloren-common-systems", path = "../common/systems" } common-systems = { package = "veloren-common-systems", path = "../common/systems" }
common-net = { package = "veloren-common-net", path = "../common/net" } common-net = { package = "veloren-common-net", path = "../common/net" }
world = { package = "veloren-world", path = "../world" } 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" # inline_tweak = "1.0.8"
@ -33,6 +33,7 @@ vek = { version = "0.14.1", features = ["serde"] }
futures-util = "0.3.7" futures-util = "0.3.7"
tokio = { version = "1", default-features = false, features = ["rt"] } tokio = { version = "1", default-features = false, features = ["rt"] }
prometheus-hyper = "0.1.2" prometheus-hyper = "0.1.2"
quinn = "0.7.2"
atomicwrites = "0.3.0" atomicwrites = "0.3.0"
chrono = { version = "0.4.9", features = ["serde"] } chrono = { version = "0.4.9", features = ["serde"] }
humantime = "2.1.0" humantime = "2.1.0"

View File

@ -398,6 +398,38 @@ impl Server {
}); });
runtime.block_on(network.listen(ListenAddr::Tcp(settings.gameserver_address)))?; runtime.block_on(network.listen(ListenAddr::Tcp(settings.gameserver_address)))?;
runtime.block_on(network.listen(ListenAddr::Mpsc(14004)))?; runtime.block_on(network.listen(ListenAddr::Mpsc(14004)))?;
if let Some(quic) = &settings.quic_files {
use std::fs;
match || -> Result<_, Box<dyn std::error::Error>> {
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); let connection_handler = ConnectionHandler::new(network, &runtime);
// Initiate real-time world simulation // Initiate real-time world simulation

View File

@ -33,12 +33,19 @@ const BANLIST_FILENAME: &str = "banlist.ron";
const SERVER_DESCRIPTION_FILENAME: &str = "description.ron"; const SERVER_DESCRIPTION_FILENAME: &str = "description.ron";
const ADMINS_FILENAME: &str = "admins.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)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Settings { pub struct Settings {
pub gameserver_address: SocketAddr, pub gameserver_address: SocketAddr,
pub metrics_address: SocketAddr, pub metrics_address: SocketAddr,
pub auth_server_address: Option<String>, pub auth_server_address: Option<String>,
pub quic_files: Option<X509FilePair>,
pub max_players: usize, pub max_players: usize,
pub world_seed: u32, pub world_seed: u32,
//pub pvp_enabled: bool, //pub pvp_enabled: bool,
@ -62,6 +69,7 @@ impl Default for Settings {
gameserver_address: SocketAddr::from(([0; 4], 14004)), gameserver_address: SocketAddr::from(([0; 4], 14004)),
metrics_address: SocketAddr::from(([0; 4], 14005)), metrics_address: SocketAddr::from(([0; 4], 14005)),
auth_server_address: Some("https://auth.veloren.net".into()), auth_server_address: Some("https://auth.veloren.net".into()),
quic_files: None,
world_seed: DEFAULT_WORLD_SEED, world_seed: DEFAULT_WORLD_SEED,
server_name: "Veloren Alpha".into(), server_name: "Veloren Alpha".into(),
max_players: 100, max_players: 100,
@ -140,6 +148,7 @@ impl Settings {
pick_unused_port().expect("Failed to find unused port!"), pick_unused_port().expect("Failed to find unused port!"),
)), )),
auth_server_address: None, auth_server_address: None,
quic_files: None,
// If loading the default map file, make sure the seed is also default. // If loading the default map file, make sure the seed is also default.
world_seed: if load.map_file.is_some() { world_seed: if load.map_file.is_some() {
load.world_seed load.world_seed

View File

@ -31,12 +31,6 @@ pub enum Msg {
Done(Result<Client, Error>), Done(Result<Client, Error>),
} }
pub enum ClientConnArgs {
Host(String),
#[allow(dead_code)] //singleplayer
Resolved(ConnectionArgs),
}
pub struct AuthTrust(String, bool); pub struct AuthTrust(String, bool);
// Used to asynchronously parse the server address, resolve host names, // Used to asynchronously parse the server address, resolve host names,
@ -51,7 +45,7 @@ impl ClientInit {
#[allow(clippy::op_ref)] // TODO: Pending review in #587 #[allow(clippy::op_ref)] // TODO: Pending review in #587
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
pub fn new( pub fn new(
connection_args: ClientConnArgs, connection_args: ConnectionArgs,
username: String, username: String,
view_distance: Option<u32>, view_distance: Option<u32>,
password: String, password: String,
@ -89,18 +83,6 @@ impl ClientInit {
.unwrap_or(false) .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; let mut last_err = None;
const FOUR_MINUTES_RETRIES: u64 = 48; const FOUR_MINUTES_RETRIES: u64 = 48;

View File

@ -5,22 +5,18 @@ use super::char_selection::CharSelectionState;
#[cfg(feature = "singleplayer")] #[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer; use crate::singleplayer::Singleplayer;
use crate::{ use crate::{
i18n::{Localization, LocalizationHandle}, i18n::LocalizationHandle, render::Renderer, settings::Settings, window::Event, Direction,
render::Renderer, GlobalState, PlayState, PlayStateResult,
settings::Settings,
window::Event,
Direction, GlobalState, PlayState, PlayStateResult,
}; };
#[cfg(feature = "singleplayer")]
use client::addr::ConnectionArgs;
use client::{ use client::{
addr::ConnectionArgs,
error::{InitProtocolError, NetworkConnectError, NetworkError}, error::{InitProtocolError, NetworkConnectError, NetworkError},
ServerInfo, 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::comp;
use common_base::span; use common_base::span;
use std::{fmt::Debug, sync::Arc}; use std::sync::Arc;
use tokio::runtime; use tokio::runtime;
use tracing::error; use tracing::error;
use ui::{Event as MainMenuEvent, MainMenuUi}; use ui::{Event as MainMenuEvent, MainMenuUi};
@ -78,7 +74,7 @@ impl PlayState for MainMenuState {
&mut global_state.info_message, &mut global_state.info_message,
"singleplayer".to_owned(), "singleplayer".to_owned(),
"".to_owned(), "".to_owned(),
ClientConnArgs::Resolved(ConnectionArgs::Mpsc(14004)), ConnectionArgs::Mpsc(14004),
&mut self.client_init, &mut self.client_init,
Some(runtime), Some(runtime),
); );
@ -120,117 +116,14 @@ impl PlayState for MainMenuState {
std::rc::Rc::new(std::cell::RefCell::new(client)), std::rc::Rc::new(std::cell::RefCell::new(client)),
))); )));
}, },
Some(InitMsg::Done(Err(err))) => { Some(InitMsg::Done(Err(e))) => {
let localized_strings = global_state.i18n.read();
self.client_init = None; self.client_init = None;
global_state.info_message = Some({ tracing::trace!(?e, "raw Client Init error");
let err = match err { let e = get_client_msg_error(e, &global_state.i18n);
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 // Log error for possible additional use later or incase that the error
// displayed is cut of. // displayed is cut of.
error!("{}", err); error!(?e, "Client Init failed");
err global_state.info_message = Some(e);
});
}, },
Some(InitMsg::IsAuthTrusted(auth_server)) => { Some(InitMsg::IsAuthTrusted(auth_server)) => {
if global_state if global_state
@ -264,6 +157,7 @@ impl PlayState for MainMenuState {
server_address, server_address,
} => { } => {
let mut net_settings = &mut global_state.settings.networking; let mut net_settings = &mut global_state.settings.networking;
let use_quic = net_settings.use_quic;
net_settings.username = username.clone(); net_settings.username = username.clone();
net_settings.default_server = server_address.clone(); net_settings.default_server = server_address.clone();
if !net_settings.servers.contains(&server_address) { if !net_settings.servers.contains(&server_address) {
@ -271,12 +165,23 @@ impl PlayState for MainMenuState {
} }
global_state.settings.save_to_file_warn(); 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( attempt_login(
&mut global_state.settings, &mut global_state.settings,
&mut global_state.info_message, &mut global_state.info_message,
username, username,
password, password,
ClientConnArgs::Host(server_address), connection_args,
&mut self.client_init, &mut self.client_init,
None, None,
); );
@ -347,14 +252,13 @@ impl PlayState for MainMenuState {
} }
} }
/// When a network error is received and there is a mismatch between the client fn get_client_msg_error(e: client_init::Error, localized_strings: &LocalizationHandle) -> String {
/// and server version it is almost definitely due to this mismatch rather than let localization = localized_strings.read();
/// a true networking error.
fn get_network_error_text( // When a network error is received and there is a mismatch between the client
localization: &Localization, // and server version it is almost definitely due to this mismatch rather than
error: impl Debug, // a true networking error.
mismatched_server_info: Option<ServerInfo>, let net_e = |error: String, mismatched_server_info: Option<ServerInfo>| -> String {
) -> String {
if let Some(server_info) = mismatched_server_info { if let Some(server_info) = mismatched_server_info {
format!( format!(
"{} {}: {} {}: {}", "{} {}: {} {}: {}",
@ -366,18 +270,97 @@ fn get_network_error_text(
) )
} else { } else {
format!( format!(
"{}: {:?}", "{}: {}",
localization.get("main.login.network_error"), localization.get("main.login.network_error"),
error error
) )
} }
};
use client::Error;
match e {
InitError::NoAddress => localization.get("main.login.server_not_found").into(),
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::ServerWentMad => localization
.get("main.login.outdated_client_or_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( fn attempt_login(
settings: &mut Settings, settings: &mut Settings,
info_message: &mut Option<String>, info_message: &mut Option<String>,
username: String, username: String,
password: String, password: String,
connection_args: ClientConnArgs, connection_args: ConnectionArgs,
client_init: &mut Option<ClientInit>, client_init: &mut Option<ClientInit>,
runtime: Option<Arc<runtime::Runtime>>, runtime: Option<Arc<runtime::Runtime>>,
) { ) {

View File

@ -9,6 +9,7 @@ pub struct NetworkingSettings {
pub servers: Vec<String>, pub servers: Vec<String>,
pub default_server: String, pub default_server: String,
pub trusted_auth_servers: HashSet<String>, pub trusted_auth_servers: HashSet<String>,
pub use_quic: bool,
} }
impl Default for NetworkingSettings { impl Default for NetworkingSettings {
@ -21,6 +22,7 @@ impl Default for NetworkingSettings {
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(), .collect(),
use_quic: false,
} }
} }
} }