mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
rumor has it, that xMAC is so annoyed by people just not updating their game and be always blammed that its a network issue, that he just implements some dummy packages.
The Server sends some known structs on a stream. Every Client from now on knows what to expect. If the list has changed, it can be assumed its incompatible anyway. If some serialisations or checks fail, we detected a wrong version. WARNING: it might happen to exist a server/client combination where this check does not work, but the versions are still incompatible. There is no guarantee, i am just trying to get rid of 90% of the discord pings
This commit is contained in:
parent
21b20ea75e
commit
7415ddd18d
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- Server kicks old client when a user is trying to log in again (often the case when a user's original connection gets dropped)
|
||||
- Client has better detection for incompatible versions
|
||||
- Added a raycast check to beams to prevent their effect applying through walls
|
||||
|
||||
## [0.9.0] - 2021-03-20
|
||||
|
@ -45,7 +45,8 @@ https://veloren.net/account/."#,
|
||||
"main.login.insecure_auth_scheme": "The auth Scheme HTTP is NOT supported. It's insecure! For development purposes, HTTP is allowed for 'localhost' or debug builds",
|
||||
"main.login.server_full": "Server is full",
|
||||
"main.login.untrusted_auth_server": "Auth server not trusted",
|
||||
"main.login.outdated_client_or_server": "ServerWentMad: Probably versions are incompatible, check for updates.",
|
||||
"main.login.prob_outdated_client_or_server": "ServerWentMad: Probably versions are incompatible, check for updates.",
|
||||
"main.login.outdated_client_or_server": "Version check failed: versions are incompatible, check for updates!",
|
||||
"main.login.timeout": "Timeout: Server did not respond in time. (Overloaded or network issues).",
|
||||
"main.login.server_shut_down": "Server shut down",
|
||||
"main.login.already_logged_in": "You are already logged into the server.",
|
||||
|
@ -8,6 +8,7 @@ pub enum Error {
|
||||
NetworkErr(NetworkError),
|
||||
ParticipantErr(ParticipantError),
|
||||
StreamErr(StreamError),
|
||||
ServerValidationFailed(String, String),
|
||||
ServerWentMad,
|
||||
ServerTimeout,
|
||||
ServerShutdown,
|
||||
|
@ -5,6 +5,7 @@
|
||||
pub mod addr;
|
||||
pub mod cmd;
|
||||
pub mod error;
|
||||
pub mod validate_version;
|
||||
|
||||
// Reexports
|
||||
pub use crate::error::Error;
|
||||
@ -225,6 +226,7 @@ impl Client {
|
||||
let character_screen_stream = participant.opened().await?;
|
||||
let in_game_stream = participant.opened().await?;
|
||||
let terrain_stream = participant.opened().await?;
|
||||
let validation_stream = participant.opened().await?;
|
||||
|
||||
register_stream.send(ClientType::Game)?;
|
||||
let server_info: ServerInfo = register_stream.recv().await?;
|
||||
@ -243,6 +245,22 @@ impl Client {
|
||||
|
||||
ping_stream.send(PingMsg::Ping)?;
|
||||
|
||||
// Validate Server dump
|
||||
if let Err(()) = validate_version::validate_dump_from_server(validation_stream).await {
|
||||
error!(
|
||||
"Client does not pass server dump validation, versions do not match. \
|
||||
Install/Update the right version"
|
||||
);
|
||||
return Err(Error::ServerValidationFailed(
|
||||
format!("{}[{}]", server_info.git_hash, server_info.git_date),
|
||||
format!(
|
||||
"{}[{}]",
|
||||
common::util::GIT_HASH.to_string(),
|
||||
common::util::GIT_DATE.to_string()
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
// Wait for initial sync
|
||||
let mut ping_interval = tokio::time::interval(core::time::Duration::from_secs(1));
|
||||
let (
|
||||
|
145
client/src/validate_version.rs
Normal file
145
client/src/validate_version.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use tracing::{debug, trace};
|
||||
|
||||
/// assume deserialization is exactly what we hardcoded.
|
||||
/// When it fails, we can generate a wrong version error
|
||||
/// message and people stop annoying xMAC94x
|
||||
/// see src/server/connecton_handler.rs
|
||||
pub(crate) async fn validate_dump_from_server(
|
||||
mut validate_stream: network::Stream,
|
||||
) -> Result<(), ()> {
|
||||
use common::{
|
||||
character::Character,
|
||||
comp::{
|
||||
inventory::Inventory,
|
||||
item::{tool::ToolKind, Reagent},
|
||||
skills::SkillGroupKind,
|
||||
},
|
||||
outcome::Outcome,
|
||||
terrain::{biome::BiomeKind, TerrainChunkMeta},
|
||||
trade::{Good, PendingTrade, Trades},
|
||||
uid::Uid,
|
||||
};
|
||||
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
|
||||
use std::collections::HashMap;
|
||||
use vek::*;
|
||||
|
||||
trace!("check Character (1)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::CharacterListUpdate(vec)) => {
|
||||
check_eq(&vec.len(), 1)?;
|
||||
let item = &vec[0];
|
||||
check_eq(&item.character, Character {
|
||||
id: Some(1337),
|
||||
alias: "foobar".to_owned(),
|
||||
})?;
|
||||
check(item.body.is_humanoid())?;
|
||||
let inv = Inventory::new_empty()
|
||||
.equipped_items()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let inv_remote = item.inventory.equipped_items().cloned().collect::<Vec<_>>();
|
||||
check_eq(&inv_remote, inv)?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
trace!("check Outcomes (2)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::Outcomes(vec)) => {
|
||||
check_eq(&vec.len(), 3)?;
|
||||
check_eq(&vec[0], Outcome::Explosion {
|
||||
pos: Vec3::new(1.0, 2.0, 3.0),
|
||||
power: 4.0,
|
||||
radius: 5.0,
|
||||
is_attack: true,
|
||||
reagent: Some(Reagent::Blue),
|
||||
})?;
|
||||
check_eq(&vec[1], Outcome::SkillPointGain {
|
||||
uid: Uid::from(1337u64),
|
||||
pos: Vec3::new(2.0, 4.0, 6.0),
|
||||
skill_tree: SkillGroupKind::Weapon(ToolKind::Empty),
|
||||
total_points: 99,
|
||||
})?;
|
||||
check_eq(&vec[2], Outcome::BreakBlock {
|
||||
pos: Vec3::new(1, 2, 3),
|
||||
color: Some(Rgb::new(0u8, 8u8, 13u8)),
|
||||
})?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
trace!("check Terrain (3)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::TerrainChunkUpdate { key, chunk }) => {
|
||||
check_eq(&key, Vec2::new(42, 1337))?;
|
||||
let chunk = chunk?;
|
||||
check_eq(chunk.meta(), TerrainChunkMeta::void())?;
|
||||
check_eq(&chunk.get_min_z(), 5)?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
trace!("check Componity Sync (4)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::CompSync(item)) => {
|
||||
check_eq(&item.comp_updates.len(), 2)?;
|
||||
//check_eq(&item.comp_updates[0],
|
||||
// CompUpdateKind::Inserted(comp::Pos(Vec3::new(42.1337, 0.0,
|
||||
// 0.0))))?; check_eq(&item.comp_updates[1],
|
||||
// CompUpdateKind::Inserted(comp::Pos(Vec3::new(0.0, 42.1337,
|
||||
// 0.0))))?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
trace!("check Pending Trade (5)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::UpdatePendingTrade(tradeid, pending)) => {
|
||||
let uid = Uid::from(70);
|
||||
check_eq(&tradeid, Trades::default().begin_trade(uid, uid))?;
|
||||
check_eq(&pending, PendingTrade::new(uid, Uid::from(71)))?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
trace!("check Economy (6)");
|
||||
match validate_stream.recv::<ServerGeneral>().await {
|
||||
Ok(ServerGeneral::SiteEconomy(info)) => {
|
||||
check_eq(&info, EconomyInfo {
|
||||
id: 99,
|
||||
population: 55,
|
||||
stock: vec![
|
||||
(Good::Wood, 50.0),
|
||||
(Good::Tools, 33.3),
|
||||
(Good::Coin, 9000.1),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<Good, f32>>(),
|
||||
labor_values: HashMap::new(),
|
||||
values: vec![
|
||||
(Good::RoadSecurity, 1.0),
|
||||
(Good::Terrain(BiomeKind::Forest), 1.0),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<Good, f32>>(),
|
||||
labors: Vec::new(),
|
||||
last_exports: HashMap::new(),
|
||||
resources: HashMap::new(),
|
||||
})?;
|
||||
},
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_eq<T: PartialEq + core::fmt::Debug>(one: &T, two: T) -> Result<(), ()> {
|
||||
if one == &two {
|
||||
Ok(())
|
||||
} else {
|
||||
debug!(?one, ?two, "failed check");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check(b: bool) -> Result<(), ()> { if b { Ok(()) } else { Err(()) } }
|
@ -145,7 +145,7 @@ pub enum SiteKind {
|
||||
Tree,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct EconomyInfo {
|
||||
pub id: SiteId,
|
||||
pub population: u32,
|
||||
|
@ -8,7 +8,7 @@ use vek::*;
|
||||
/// occur, nor is it something that may be cancelled or otherwise altered. Its
|
||||
/// primary purpose is to act as something for frontends (both server and
|
||||
/// client) to listen to in order to receive feedback about events in the world.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Outcome {
|
||||
Explosion {
|
||||
pos: Vec3<f32>,
|
||||
|
@ -78,7 +78,7 @@ impl TerrainChunkSize {
|
||||
|
||||
// TerrainChunkMeta
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TerrainChunkMeta {
|
||||
name: Option<String>,
|
||||
biome: BiomeKind,
|
||||
|
@ -88,6 +88,121 @@ impl ConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// This code just generates some messages so that the client can assume
|
||||
/// deserialization This is completely random chosen with the goal to
|
||||
/// cover as much as possible and to stop xMAC94x being annoyed in
|
||||
/// discord by people that just have a old version
|
||||
fn dump_messages_so_client_can_validate_itself(
|
||||
mut validate_stream: network::Stream,
|
||||
) -> Result<(), network::StreamError> {
|
||||
use common::{
|
||||
character::{Character, CharacterItem},
|
||||
comp,
|
||||
comp::{
|
||||
humanoid,
|
||||
inventory::Inventory,
|
||||
item::{tool::ToolKind, Reagent},
|
||||
skills::SkillGroupKind,
|
||||
},
|
||||
outcome::Outcome,
|
||||
terrain::{biome::BiomeKind, Block, BlockKind, TerrainChunk, TerrainChunkMeta},
|
||||
trade::{Good, PendingTrade, Trades},
|
||||
uid::Uid,
|
||||
};
|
||||
use common_net::{
|
||||
msg::{world_msg::EconomyInfo, EcsCompPacket, ServerGeneral},
|
||||
sync::CompSyncPackage,
|
||||
};
|
||||
use rand::SeedableRng;
|
||||
use std::collections::HashMap;
|
||||
use vek::*;
|
||||
|
||||
// 1. Simulate Character
|
||||
let mut rng = rand::rngs::SmallRng::from_seed([42; 32]);
|
||||
let item = CharacterItem {
|
||||
character: Character {
|
||||
id: Some(1337),
|
||||
alias: "foobar".to_owned(),
|
||||
},
|
||||
body: comp::Body::Humanoid(humanoid::Body::random_with(
|
||||
&mut rng,
|
||||
&humanoid::Species::Undead,
|
||||
)),
|
||||
inventory: Inventory::new_empty(),
|
||||
};
|
||||
validate_stream.send(ServerGeneral::CharacterListUpdate(vec![item]))?;
|
||||
|
||||
// 2. Simulate Outcomes
|
||||
let item1 = Outcome::Explosion {
|
||||
pos: Vec3::new(1.0, 2.0, 3.0),
|
||||
power: 4.0,
|
||||
radius: 5.0,
|
||||
is_attack: true,
|
||||
reagent: Some(Reagent::Blue),
|
||||
};
|
||||
let item2 = Outcome::SkillPointGain {
|
||||
uid: Uid::from(1337u64),
|
||||
pos: Vec3::new(2.0, 4.0, 6.0),
|
||||
skill_tree: SkillGroupKind::Weapon(ToolKind::Empty),
|
||||
total_points: 99,
|
||||
};
|
||||
let item3 = Outcome::BreakBlock {
|
||||
pos: Vec3::new(1, 2, 3),
|
||||
color: Some(Rgb::new(0u8, 8u8, 13u8)),
|
||||
};
|
||||
validate_stream.send(ServerGeneral::Outcomes(vec![item1, item2, item3]))?;
|
||||
|
||||
// 3. Simulate Terrain
|
||||
let item = TerrainChunk::new(
|
||||
5,
|
||||
Block::new(BlockKind::Water, Rgb::zero()),
|
||||
Block::new(BlockKind::Air, Rgb::zero()),
|
||||
TerrainChunkMeta::void(),
|
||||
);
|
||||
validate_stream.send(ServerGeneral::TerrainChunkUpdate {
|
||||
key: Vec2::new(42, 1337),
|
||||
chunk: Ok(Box::new(item)),
|
||||
})?;
|
||||
|
||||
// 4. Simulate Componity Sync
|
||||
let mut item = CompSyncPackage::<EcsCompPacket>::new();
|
||||
let uid = Uid::from(70);
|
||||
item.comp_inserted(uid, comp::Pos(Vec3::new(42.1337, 0.0, 0.0)));
|
||||
item.comp_inserted(uid, comp::Vel(Vec3::new(0.0, 42.1337, 0.0)));
|
||||
validate_stream.send(ServerGeneral::CompSync(item))?;
|
||||
|
||||
// 5. Pending Trade
|
||||
let uid = Uid::from(70);
|
||||
validate_stream.send(ServerGeneral::UpdatePendingTrade(
|
||||
Trades::default().begin_trade(uid, uid),
|
||||
PendingTrade::new(uid, Uid::from(71)),
|
||||
))?;
|
||||
|
||||
// 6. Economy Info
|
||||
validate_stream.send(ServerGeneral::SiteEconomy(EconomyInfo {
|
||||
id: 99,
|
||||
population: 55,
|
||||
stock: vec![
|
||||
(Good::Wood, 50.0),
|
||||
(Good::Tools, 33.3),
|
||||
(Good::Coin, 9000.1),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<Good, f32>>(),
|
||||
labor_values: HashMap::new(),
|
||||
values: vec![
|
||||
(Good::RoadSecurity, 1.0),
|
||||
(Good::Terrain(BiomeKind::Forest), 1.0),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<Good, f32>>(),
|
||||
labors: Vec::new(),
|
||||
last_exports: HashMap::new(),
|
||||
resources: HashMap::new(),
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn init_participant(
|
||||
participant: Participant,
|
||||
client_sender: Sender<IncomingClient>,
|
||||
@ -106,6 +221,7 @@ impl ConnectionHandler {
|
||||
let character_screen_stream = participant.open(3, reliablec, 500).await?;
|
||||
let in_game_stream = participant.open(3, reliablec, 100_000).await?;
|
||||
let terrain_stream = participant.open(4, reliablec, 20_000).await?;
|
||||
let validate_stream = participant.open(4, reliablec, 1_000_000).await?;
|
||||
|
||||
let server_data = receiver.recv()?;
|
||||
|
||||
@ -123,6 +239,11 @@ impl ConnectionHandler {
|
||||
Some(client_type) => client_type?,
|
||||
};
|
||||
|
||||
if let Err(e) = Self::dump_messages_so_client_can_validate_itself(validate_stream) {
|
||||
trace!(?e, ?client_type, "a client dropped as he failed validation");
|
||||
return Err(e.into());
|
||||
};
|
||||
|
||||
let client = Client::new(
|
||||
client_type,
|
||||
participant,
|
||||
|
@ -139,8 +139,14 @@ impl PlayState for MainMenuState {
|
||||
client::Error::AuthServerNotTrusted => localized_strings
|
||||
.get("main.login.untrusted_auth_server")
|
||||
.into(),
|
||||
client::Error::ServerValidationFailed(server, client) => format!(
|
||||
"{}: server version {}, client version {}",
|
||||
localized_strings.get("main.login.outdated_client_or_server"),
|
||||
server,
|
||||
client,
|
||||
),
|
||||
client::Error::ServerWentMad => localized_strings
|
||||
.get("main.login.outdated_client_or_server")
|
||||
.get("main.login.prob_outdated_client_or_server")
|
||||
.into(),
|
||||
client::Error::ServerTimeout => {
|
||||
localized_strings.get("main.login.timeout").into()
|
||||
|
Loading…
Reference in New Issue
Block a user