Browse Source

Merge branch 'revert-c6e200a3' into 'master'

Add a configuration Option to opt-in Quic backend

See merge request veloren/veloren!2318
merge-requests/2356/merge
Marcel 2 months ago
parent
commit
f988584337
  1. 2
      Cargo.lock
  2. 3
      client/Cargo.toml
  3. 187
      client/src/addr.rs
  4. 2
      client/src/error.rs
  5. 45
      client/src/lib.rs
  6. 1
      common/frontend/src/lib.rs
  7. 2
      network/src/channel.rs
  8. 38
      network/src/participant.rs
  9. 2
      network/tests/helper.rs
  10. 3
      server/Cargo.toml
  11. 36
      server/src/lib.rs
  12. 9
      server/src/settings.rs
  13. 8
      voxygen/src/menu/char_selection/mod.rs
  14. 30
      voxygen/src/menu/main/client_init.rs
  15. 273
      voxygen/src/menu/main/mod.rs
  16. 2
      voxygen/src/settings/networking.rs

2
Cargo.lock

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

3
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 }

187
client/src/addr.rs

@ -4,47 +4,80 @@ use tracing::trace;
#[derive(Clone, Debug)]
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),
}
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(
/* <hostname/ip>:[<port>] */ server_address: &str,
prefer_ipv6: bool,
) -> Result<Self, std::io::Error> {
// `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<Vec<SocketAddr>, 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<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(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<Item = SocketAddr>, prefer_ipv6: bool) -> Self {
let (mut first_addrs, mut second_addrs) =
s.partition::<Vec<_>, _>(|a| a.is_ipv6() == prefer_ipv6);
let addr = std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..))
.collect::<Vec<_>>();
ConnectionArgs::IpAndPort(addr)
}
fn sort_ipv6(s: impl Iterator<Item = SocketAddr>, prefer_ipv6: bool) -> Vec<SocketAddr> {
let (mut first_addrs, mut second_addrs) =
s.partition::<Vec<_>, _>(|a| a.is_ipv6() == prefer_ipv6);
std::iter::Iterator::chain(first_addrs.drain(..), second_addrs.drain(..)).collect::<Vec<_>>()
}
#[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));
}
}

2
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,

45
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<u32>,
runtime: Arc<Runtime>,
// TODO: refactor to avoid needing to use this out parameter
mismatched_server_info: &mut Option<ServerInfo>,
@ -214,20 +212,23 @@ 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,
} => {
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())
})
.await?
},
ConnectionArgs::Mpsc(id) => network.connect(ConnectAddr::Mpsc(id)).await?,
};
@ -694,7 +695,7 @@ impl Client {
tick: 0,
state,
view_distance,
view_distance: None,
loaded_distance: 0.0,
pending_chunks: HashMap::new(),
@ -2452,7 +2453,6 @@ impl Drop for Client {
#[cfg(test)]
mod tests {
use super::*;
use std::net::SocketAddr;
#[test]
/// THIS TEST VERIFIES THE CONSTANT API.
@ -2462,17 +2462,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<u32> = None;
let runtime = Arc::new(Runtime::new().unwrap());
let runtime2 = Arc::clone(&runtime);
let veloren_client: Result<Client, Error> = 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,
));

1
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()

2
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) => {

38
network/src/participant.rs

@ -193,24 +193,30 @@ impl BParticipant {
fn best_protocol(all: &SortedVec<Cid, SendProtocols>, promises: Promises) -> Option<Cid> {
// 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::<crate::channel::TcpDrain>::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::<crate::channel::TcpDrain>::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::<crate::channel::QuicDrain>::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<Sid, Cid> to know the respective protocol

2
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");

3
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"

36
server/src/lib.rs

@ -397,6 +397,42 @@ 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<dyn std::error::Error>> {
let mut server_config =
quinn::ServerConfigBuilder::new(quinn::ServerConfig::default());
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) => {
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, ?settings.quic_files, "Failed to load Quic Certificate, run without Quic")
},
}
}
let connection_handler = ConnectionHandler::new(network, &runtime);
// Initiate real-time world simulation

9
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<String>,
pub quic_files: Option<X509FilePair>,
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

8
voxygen/src/menu/char_selection/mod.rs

@ -115,8 +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);
{
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),

30
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<ServerInfo>,
@ -31,12 +30,6 @@ pub enum Msg {
Done(Result<Client, Error>),
}
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<u32>,
password: String,
runtime: Option<Arc<runtime::Runtime>>,
) -> 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));

273
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<ServerInfo>,
) -> 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<ServerInfo>| -> 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<String>,
username: String,
password: String,
connection_args: ClientConnArgs,
connection_args: ConnectionArgs,
client_init: &mut Option<ClientInit>,
runtime: Option<Arc<runtime::Runtime>>,
) {
@ -391,7 +367,6 @@ fn attempt_login(
*client_init = Some(ClientInit::new(
connection_args,
username,
Some(settings.graphics.view_distance),
password,
runtime,
));

2
voxygen/src/settings/networking.rs

@ -9,6 +9,7 @@ pub struct NetworkingSettings {
pub servers: Vec<String>,
pub default_server: String,
pub trusted_auth_servers: HashSet<String>,
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,
}
}
}

Loading…
Cancel
Save