mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add SRV lookup functionality to voxygen
This will make voxygen issue a SRV lookup when connecting to a host, allowing server owners to configure non-standard ports for servers and host servers using vanity domains easily. It additionally allows servers to be hosted on both QUIC and TCP at the same time, with the client connecting to the preferred protocol automatically, but gracefully falling back if a connection is not possible.
This commit is contained in:
parent
d3a97ba34c
commit
2d2ffa2b10
143
Cargo.lock
generated
143
Cargo.lock
generated
@ -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"
|
||||
|
@ -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 }
|
||||
|
@ -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));
|
||||
|
@ -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?,
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user