Merge branch 'dequbed/srv-lookup' into 'master'

Add SRV lookup functionality to voxygen

See merge request veloren/veloren!4310
This commit is contained in:
maxicarlos08 2024-02-07 14:17:37 +00:00
commit ba0fc08f8d
6 changed files with 343 additions and 12 deletions

143
Cargo.lock generated
View File

@ -1718,7 +1718,7 @@ dependencies = [
"tokio",
"tracing",
"url",
"winreg",
"winreg 0.51.0",
]
[[package]]
@ -1847,6 +1847,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enum-as-inner"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
"heck 0.4.1",
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 2.0.48",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
@ -2750,6 +2762,51 @@ dependencies = [
"rayon",
]
[[package]]
name = "hickory-proto"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
dependencies = [
"async-trait",
"cfg-if 1.0.0",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna 0.4.0",
"ipnet",
"once_cell",
"rand 0.8.5",
"thiserror",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "hickory-resolver"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
dependencies = [
"cfg-if 1.0.0",
"futures-util",
"hickory-proto",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot 0.12.1",
"rand 0.8.5",
"resolv-conf",
"smallvec",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "home"
version = "0.5.9"
@ -2759,6 +2816,17 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]]
name = "http"
version = "0.2.11"
@ -2937,6 +3005,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -3070,6 +3148,24 @@ dependencies = [
"mach2",
]
[[package]]
name = "ipconfig"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2",
"widestring",
"windows-sys 0.48.0",
"winreg 0.50.0",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "is-terminal"
version = "0.4.10"
@ -3399,6 +3495,15 @@ dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "lz-fear"
version = "0.1.1"
@ -3455,6 +3560,12 @@ dependencies = [
"libc",
]
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "matchers"
version = "0.1.0"
@ -4695,6 +4806,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.30.0"
@ -5103,6 +5220,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
[[package]]
name = "resolv-conf"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
dependencies = [
"hostname",
"quick-error",
]
[[package]]
name = "ring"
version = "0.16.20"
@ -6778,7 +6905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
"idna 0.5.0",
"percent-encoding",
]
@ -6852,11 +6979,13 @@ dependencies = [
"byteorder",
"clap",
"hashbrown 0.13.2",
"hickory-resolver",
"image",
"num 0.4.1",
"quinn",
"rayon",
"ron",
"rustls",
"rustyline",
"serde",
"specs",
@ -8423,6 +8552,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if 1.0.0",
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.51.0"

View File

@ -23,7 +23,9 @@ network = { package = "veloren-network", path = "../network", features = ["compr
byteorder = "1.3.2"
tokio = { workspace = true, features = ["rt-multi-thread"] }
quinn = "0.10"
quinn = { version = "0.10", features = ["rustls"] }
rustls = { version = "0.21.6", features = ["dangerous_configuration"] }
hickory-resolver = { version = "0.24.0", features = ["system-config", "tokio-runtime"] }
image = { workspace = true }
num = { workspace = true }
tracing = { workspace = true }

View File

@ -8,12 +8,24 @@ pub enum ConnectionArgs {
Quic {
hostname: String,
prefer_ipv6: bool,
validate_tls: bool,
},
///hostname: (hostname|ip):[<port>]
Tcp {
hostname: String,
prefer_ipv6: bool,
},
/// SRV lookup
///
/// SRV lookups can not contain a port, but will be able to connect to
/// configured port automatically. If a connection with a port is given,
/// this will gracefully fall back to TCP.
Srv {
hostname: String,
prefer_ipv6: bool,
validate_tls: bool,
use_quic: bool,
},
Mpsc(u64),
}
@ -51,6 +63,7 @@ pub(crate) async fn resolve(
pub(crate) async fn try_connect<F>(
network: &network::Network,
address: &str,
override_port: Option<u16>,
prefer_ipv6: bool,
f: F,
) -> Result<network::Participant, crate::error::Error>
@ -59,10 +72,15 @@ where
{
use crate::error::Error;
let mut participant = None;
for addr in resolve(address, prefer_ipv6)
for mut addr in resolve(address, prefer_ipv6)
.await
.map_err(Error::HostnameLookupFailed)?
{
// Override the port if one was passed. Used for SRV lookups which get port info
// out-of-band
if let Some(port) = override_port {
addr.set_port(port);
}
match network.connect(f(addr)).await {
Ok(p) => {
participant = Some(Ok(p));

View File

@ -68,16 +68,22 @@ use common_state::State;
use common_systems::add_local_systems;
use comp::BuffKind;
use hashbrown::{HashMap, HashSet};
use hickory_resolver::{
config::{ResolverConfig, ResolverOpts},
AsyncResolver,
};
use image::DynamicImage;
use network::{ConnectAddr, Network, Participant, Pid, Stream};
use num::traits::FloatConst;
use rayon::prelude::*;
use rustls::client::ServerCertVerified;
use specs::Component;
use std::{
collections::{BTreeMap, VecDeque},
fmt::Debug,
mem,
sync::Arc,
time::{Duration, Instant},
time::{Duration, Instant, SystemTime},
};
use tokio::runtime::Runtime;
use tracing::{debug, error, trace, warn};
@ -327,6 +333,50 @@ pub struct CharacterList {
pub loading: bool,
}
async fn connect_quic(
network: &Network,
hostname: String,
override_port: Option<u16>,
prefer_ipv6: bool,
validate_tls: bool,
) -> Result<network::Participant, crate::error::Error> {
let config = if validate_tls {
quinn::ClientConfig::with_native_roots()
} else {
warn!(
"skipping validation of server identity. There is no guarantee that the server you're \
connected to is the one you expect to be connecting to."
);
struct Verifier;
impl rustls::client::ServerCertVerifier for Verifier {
fn verify_server_cert(
&self,
_: &rustls::Certificate,
_: &[rustls::Certificate],
_: &rustls::ServerName,
_: &mut dyn Iterator<Item = &[u8]>,
_: &[u8],
_: SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
}
let mut cfg = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_custom_certificate_verifier(Arc::new(Verifier))
.with_no_client_auth();
cfg.enable_early_data = true;
quinn::ClientConfig::new(Arc::new(cfg))
};
addr::try_connect(network, &hostname, override_port, prefer_ipv6, |a| {
ConnectAddr::Quic(a, config.clone(), hostname.clone())
})
.await
}
impl Client {
pub async fn new(
addr: ConnectionArgs,
@ -343,24 +393,132 @@ impl Client {
let network = Network::new(Pid::new(), &runtime);
init_stage_update(ClientInitStage::ConnectionEstablish);
let mut participant = match addr {
ConnectionArgs::Srv {
hostname,
prefer_ipv6,
validate_tls,
use_quic,
} => {
// Try to create a resolver backed by /etc/resolv.conf or the Windows Registry
// first. If that fails, create a resolver being hard-coded to
// Google's 8.8.8.8 public resolver.
let resolver = AsyncResolver::tokio_from_system_conf().unwrap_or_else(|error| {
error!("Failed to create DNS resolver using system configuration: {error:?}");
warn!("Falling back to a default configured resolver.");
AsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default())
});
let quic_service_host = format!("_veloren._udp.{hostname}");
let quic_lookup_future = resolver.srv_lookup(quic_service_host);
let tcp_service_host = format!("_veloren._tcp.{hostname}");
let tcp_lookup_future = resolver.srv_lookup(tcp_service_host);
let (quic_rr, tcp_rr) = tokio::join!(quic_lookup_future, tcp_lookup_future);
#[derive(Eq, PartialEq)]
enum ConnMode {
Quic,
Tcp,
}
// Push the results of both futures into `srv_rr`. This uses map_or_else purely
// for side effects.
let mut srv_rr = Vec::new();
let () = quic_rr.map_or_else(
|error| {
warn!("QUIC SRV lookup failed: {error:?}");
},
|srv_lookup| {
srv_rr.extend(srv_lookup.iter().cloned().map(|srv| (ConnMode::Quic, srv)))
},
);
let () = tcp_rr.map_or_else(
|error| {
warn!("TCP SRV lookup failed: {error:?}");
},
|srv_lookup| {
srv_rr.extend(srv_lookup.iter().cloned().map(|srv| (ConnMode::Tcp, srv)))
},
);
// SRV records have a priority; lowest priority hosts MUST be contacted first.
let srv_rr_slice = srv_rr.as_mut_slice();
srv_rr_slice.sort_by_key(|(_, srv)| srv.priority());
let mut iter = srv_rr_slice.iter();
// This loops exits as soon as the above iter over `srv_rr_slice` is exhausted
loop {
if let Some((conn_mode, srv_rr)) = iter.next() {
let hostname = format!("{}", srv_rr.target());
let port = Some(srv_rr.port());
let conn_result = match conn_mode {
ConnMode::Quic => {
connect_quic(&network, hostname, port, prefer_ipv6, validate_tls)
.await
},
ConnMode::Tcp => {
addr::try_connect(
&network,
&hostname,
port,
prefer_ipv6,
ConnectAddr::Tcp,
)
.await
},
};
match conn_result {
Ok(c) => break c,
Err(error) => {
warn!("Failed to connect to host {}: {error:?}", srv_rr.target())
},
}
} else {
warn!(
"No SRV hosts succeeded connection, falling back to direct connection"
);
// This case is also hit if no SRV host was returned from the query, so we
// check for QUIC/TCP preference.
let c = if use_quic {
connect_quic(&network, hostname, None, prefer_ipv6, validate_tls)
.await?
} else {
match addr::try_connect(
&network,
&hostname,
None,
prefer_ipv6,
ConnectAddr::Tcp,
)
.await
{
Ok(c) => c,
Err(error) => return Err(error),
}
};
break c;
}
}
},
ConnectionArgs::Tcp {
hostname,
prefer_ipv6,
} => addr::try_connect(&network, &hostname, prefer_ipv6, ConnectAddr::Tcp).await?,
} => {
addr::try_connect(&network, &hostname, None, prefer_ipv6, ConnectAddr::Tcp).await?
},
ConnectionArgs::Quic {
hostname,
prefer_ipv6,
validate_tls,
} => {
warn!(
"QUIC is enabled. This is experimental and you won't be able to connect to \
TCP servers unless deactivated"
);
let config = quinn::ClientConfig::with_native_roots();
addr::try_connect(&network, &hostname, prefer_ipv6, |a| {
ConnectAddr::Quic(a, config.clone(), hostname.clone())
})
.await?
connect_quic(&network, hostname, None, prefer_ipv6, validate_tls).await?
},
ConnectionArgs::Mpsc(id) => network.connect(ConnectAddr::Mpsc(id)).await?,
};

View File

@ -334,7 +334,9 @@ impl PlayState for MainMenuState {
server_address,
} => {
let net_settings = &mut global_state.settings.networking;
let use_srv = net_settings.use_srv;
let use_quic = net_settings.use_quic;
let validate_tls = net_settings.validate_tls;
net_settings.username = username.clone();
net_settings.default_server = server_address.clone();
if !net_settings.servers.contains(&server_address) {
@ -344,10 +346,18 @@ impl PlayState for MainMenuState {
.settings
.save_to_file_warn(&global_state.config_dir);
let connection_args = if use_quic {
let connection_args = if use_srv {
ConnectionArgs::Srv {
hostname: server_address,
prefer_ipv6: false,
validate_tls,
use_quic,
}
} else if use_quic {
ConnectionArgs::Quic {
hostname: server_address,
prefer_ipv6: false,
validate_tls,
}
} else {
ConnectionArgs::Tcp {

View File

@ -9,7 +9,9 @@ pub struct NetworkingSettings {
pub servers: Vec<String>,
pub default_server: String,
pub trusted_auth_servers: HashSet<String>,
pub use_srv: bool,
pub use_quic: bool,
pub validate_tls: bool,
pub player_physics_behavior: bool,
pub lossy_terrain_compression: bool,
pub enable_discord_integration: bool,
@ -25,7 +27,9 @@ impl Default for NetworkingSettings {
.iter()
.map(|s| s.to_string())
.collect(),
use_srv: true,
use_quic: false,
validate_tls: true,
player_physics_behavior: false,
lossy_terrain_compression: false,
enable_discord_integration: true,