Send client protocol & username to server from lobby, send server brand

This commit is contained in:
timvisee
2021-11-14 14:04:51 +01:00
parent 518fca90eb
commit c9290827be
4 changed files with 81 additions and 54 deletions

4
Cargo.lock generated
View File

@@ -684,7 +684,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]] [[package]]
name = "minecraft-protocol" name = "minecraft-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#a4fc2bcf7bced11fa255c371d8d5c780157ecbb4" source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#bef4fa8c009857e3753d85d8e2df1f71c3ae08f6"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"minecraft-protocol-derive", "minecraft-protocol-derive",
@@ -697,7 +697,7 @@ dependencies = [
[[package]] [[package]]
name = "minecraft-protocol-derive" name = "minecraft-protocol-derive"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#a4fc2bcf7bced11fa255c371d8d5c780157ecbb4" source = "git+https://github.com/timvisee/rust-minecraft-protocol?branch=lazymc-v1_17_1#bef4fa8c009857e3753d85d8e2df1f71c3ae08f6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -17,8 +17,8 @@ use minecraft_protocol::version::v1_14_4::login::{LoginDisconnect, LoginStart, L
use minecraft_protocol::version::v1_14_4::status::StatusResponse; use minecraft_protocol::version::v1_14_4::status::StatusResponse;
use minecraft_protocol::version::v1_17_1::game::{ use minecraft_protocol::version::v1_17_1::game::{
ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameDisconnect, JoinGame, ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameDisconnect, JoinGame,
PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, SpawnPosition, PlayerPositionAndLook, PluginMessage, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes,
TimeUpdate, SpawnPosition, TimeUpdate,
}; };
use nbt::CompoundTag; use nbt::CompoundTag;
use tokio::io::{self, AsyncWriteExt}; use tokio::io::{self, AsyncWriteExt};
@@ -29,7 +29,7 @@ use tokio::time;
use uuid::Uuid; use uuid::Uuid;
use crate::config::*; use crate::config::*;
use crate::proto::{self, Client, ClientState, RawPacket}; use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket};
use crate::proxy; use crate::proxy;
use crate::server::{self, Server, State}; use crate::server::{self, Server, State};
use crate::service; use crate::service;
@@ -53,6 +53,7 @@ const TICKS_PER_SECOND: u32 = 20;
// TODO: on error, nicely kick client with message // TODO: on error, nicely kick client with message
pub async fn serve( pub async fn serve(
client: Client, client: Client,
client_info: ClientInfo,
mut inbound: TcpStream, mut inbound: TcpStream,
config: Arc<Config>, config: Arc<Config>,
server: Arc<Server>, server: Arc<Server>,
@@ -63,6 +64,12 @@ pub async fn serve(
// TODO: note this assumes the first receiving packet (over queue) is login start // TODO: note this assumes the first receiving packet (over queue) is login start
// TODO: assert client is in login mode! // TODO: assert client is in login mode!
// We must have useful client info
if client_info.username.is_none() {
error!(target: "lazymc::lobby", "Client username is unknown, closing connection");
return Err(());
}
// Incoming buffer and packet holding queue // Incoming buffer and packet holding queue
let mut inbound_buf = queue; let mut inbound_buf = queue;
let mut server_queue = BytesMut::new(); let mut server_queue = BytesMut::new();
@@ -99,7 +106,8 @@ pub async fn serve(
// Wait for server to come online, then set up new connection to it // Wait for server to come online, then set up new connection to it
stage_wait(config.clone(), server.clone(), &mut writer).await?; stage_wait(config.clone(), server.clone(), &mut writer).await?;
let (mut outbound, mut server_buf) = connect_to_server(client, &config, server).await?; let (mut outbound, mut server_buf) =
connect_to_server(client, client_info, &config, server).await?;
// Grab join game packet from server // Grab join game packet from server
let join_game = wait_for_server_join_game(&mut outbound, &mut server_buf).await?; let join_game = wait_for_server_join_game(&mut outbound, &mut server_buf).await?;
@@ -137,36 +145,8 @@ pub async fn serve(
return Ok(()); return Ok(());
} }
// TODO: is this ever called? // TODO: when receiving Login Plugin Request, respond with empty payload
if client_state == ClientState::Play // See: https://wiki.vg/Protocol#Login_Plugin_Request
&& packet.id == proto::packets::play::SERVER_CLIENT_SETTINGS
{
debug!(target: "lazymc::lobby", "Ignoring client settings packet");
continue;
}
// TODO: is this ever called?
if client_state == ClientState::Play
&& packet.id == proto::packets::play::SERVER_PLUGIN_MESSAGE
{
debug!(target: "lazymc::lobby", "Ignoring plugin message packet");
continue;
}
// TODO: is this ever called?
if client_state == ClientState::Play
&& packet.id == proto::packets::play::SERVER_PLAYER_POS_ROT
{
debug!(target: "lazymc::lobby", "Ignoring player pos rot packet");
continue;
}
// TODO: is this ever called?
if client_state == ClientState::Play && packet.id == proto::packets::play::SERVER_PLAYER_POS
{
debug!(target: "lazymc::lobby", "Ignoring player pos packet");
continue;
}
// Show unhandled packet warning // Show unhandled packet warning
debug!(target: "lazymc", "Received unhandled packet:"); debug!(target: "lazymc", "Received unhandled packet:");
@@ -232,7 +212,9 @@ async fn send_lobby_play_packets(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
// Send initial game join // Send initial game join
send_lobby_join_game(writer).await?; send_lobby_join_game(writer).await?;
// TODO: send plugin message for brand // Send server brand
// TODO: does client ever receive real brand after this?
send_lobby_brand(writer).await?;
// Send spawn and player position, disables 'download terrain' screen // Send spawn and player position, disables 'download terrain' screen
// TODO: is sending spawn this required? // TODO: is sending spawn this required?
@@ -286,6 +268,24 @@ async fn send_lobby_join_game(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
Ok(()) Ok(())
} }
/// Send lobby brand to client.
async fn send_lobby_brand(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let brand = b"lazymc".to_vec();
let packet = PluginMessage {
channel: "minecraft:brand".into(),
data: brand,
};
let mut data = Vec::new();
packet.encode(&mut data).map_err(|_| ())?;
let response = RawPacket::new(proto::packets::play::CLIENT_PLUGIN_MESSAGE, data).encode()?;
writer.write_all(&response).await.map_err(|_| ())?;
Ok(())
}
/// Send lobby spawn position to client. /// Send lobby spawn position to client.
async fn send_lobby_spawn_pos(writer: &mut WriteHalf<'_>) -> Result<(), ()> { async fn send_lobby_spawn_pos(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
let packet = SpawnPosition { let packet = SpawnPosition {
@@ -537,6 +537,7 @@ async fn wait_for_server<'a>(config: Arc<Config>, server: Arc<Server>) -> Result
// TODO: clean this up // TODO: clean this up
async fn connect_to_server( async fn connect_to_server(
real_client: Client, real_client: Client,
client_info: ClientInfo,
config: &Config, config: &Config,
server: Arc<Server>, server: Arc<Server>,
) -> Result<(TcpStream, BytesMut), ()> { ) -> Result<(TcpStream, BytesMut), ()> {
@@ -551,9 +552,9 @@ async fn connect_to_server(
let tmp_client = Client::default(); let tmp_client = Client::default();
tmp_client.set_state(ClientState::Login); tmp_client.set_state(ClientState::Login);
// TODO: use client version // Handshake packet
let packet = Handshake { let packet = Handshake {
protocol_version: 755, protocol_version: client_info.protocol_version.unwrap(),
server_addr: config.server.address.ip().to_string(), server_addr: config.server.address.ip().to_string(),
server_port: config.server.address.port(), server_port: config.server.address.port(),
next_state: ClientState::Login.to_id(), next_state: ClientState::Login.to_id(),
@@ -566,9 +567,8 @@ async fn connect_to_server(
writer.write_all(&request).await.map_err(|_| ())?; writer.write_all(&request).await.map_err(|_| ())?;
// Request login start // Request login start
// TODO: use client username
let packet = LoginStart { let packet = LoginStart {
name: "timvisee".into(), name: client_info.username.ok_or(())?,
}; };
let mut data = Vec::new(); let mut data = Vec::new();

View File

@@ -53,6 +53,7 @@ pub mod packets {
pub const CLIENT_CHAT_MSG: i32 = 0x0F; pub const CLIENT_CHAT_MSG: i32 = 0x0F;
pub const CLIENT_SPAWN_POS: i32 = 0x4B; pub const CLIENT_SPAWN_POS: i32 = 0x4B;
pub const CLIENT_DISCONNECT: i32 = 0x1A; pub const CLIENT_DISCONNECT: i32 = 0x1A;
pub const CLIENT_PLUGIN_MESSAGE: i32 = 0x18;
} }
} }
@@ -125,6 +126,22 @@ impl Default for ClientState {
} }
} }
/// Client info, useful during connection handling.
#[derive(Debug, Default)]
pub struct ClientInfo {
/// Client protocol version.
pub protocol_version: Option<i32>,
/// Client username.
pub username: Option<String>,
}
impl ClientInfo {
pub fn empty() -> Self {
Self::default()
}
}
/// Raw Minecraft packet. /// Raw Minecraft packet.
/// ///
/// Having a packet ID and a raw data byte array. /// Having a packet ID and a raw data byte array.

View File

@@ -17,7 +17,7 @@ use tokio::time;
use crate::config::*; use crate::config::*;
use crate::lobby; use crate::lobby;
use crate::proto::{self, Client, ClientState, RawPacket}; use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket};
use crate::server::{self, Server, State}; use crate::server::{self, Server, State};
use crate::service; use crate::service;
@@ -39,6 +39,8 @@ pub async fn serve(
|| config.join.methods.contains(&Method::Forward); || config.join.methods.contains(&Method::Forward);
let mut inbound_history = BytesMut::new(); let mut inbound_history = BytesMut::new();
let mut client_info = ClientInfo::empty();
loop { loop {
// Read packet from stream // Read packet from stream
let (packet, raw) = match proto::read_packet(&mut buf, &mut reader).await { let (packet, raw) = match proto::read_packet(&mut buf, &mut reader).await {
@@ -55,22 +57,28 @@ pub async fn serve(
// Hijack handshake // Hijack handshake
if client_state == ClientState::Handshake && packet.id == proto::STATUS_PACKET_ID_STATUS { if client_state == ClientState::Handshake && packet.id == proto::STATUS_PACKET_ID_STATUS {
// Parse handshake, grab new state // Parse handshake
let new_state = match Handshake::decode(&mut packet.data.as_slice()) { let handshake = match Handshake::decode(&mut packet.data.as_slice()) {
Ok(handshake) => match ClientState::from_id(handshake.next_state) { Ok(handshake) => handshake,
Some(state) => state,
None => {
error!(target: "lazymc", "Client tried to switch into unknown protcol state ({}), disconnecting", handshake.next_state);
break;
}
},
Err(_) => { Err(_) => {
debug!(target: "lazymc", "Got malformed handshake from client, disconnecting"); debug!(target: "lazymc", "Got malformed handshake from client, disconnecting");
break; break;
} }
}; };
// Update client state // Parse new state
let new_state = match ClientState::from_id(handshake.next_state) {
Some(state) => state,
None => {
error!(target: "lazymc", "Client tried to switch into unknown protcol state ({}), disconnecting", handshake.next_state);
break;
}
};
// Update client info and client state
client_info
.protocol_version
.replace(handshake.protocol_version);
client.set_state(new_state); client.set_state(new_state);
// If login handshake and holding is enabled, hold packets // If login handshake and holding is enabled, hold packets
@@ -103,10 +111,12 @@ pub async fn serve(
// Hijack login start // Hijack login start
if client_state == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START { if client_state == ClientState::Login && packet.id == proto::LOGIN_PACKET_ID_LOGIN_START {
// Try to get login username // Try to get login username, update client info
// TODO: we should always parse this packet successfully
let username = LoginStart::decode(&mut packet.data.as_slice()) let username = LoginStart::decode(&mut packet.data.as_slice())
.ok() .ok()
.map(|p| p.name); .map(|p| p.name);
client_info.username = username.clone();
// Kick if lockout is enabled // Kick if lockout is enabled
if config.lockout.enabled { if config.lockout.enabled {
@@ -137,7 +147,7 @@ pub async fn serve(
queue.extend(buf.split_off(0)); queue.extend(buf.split_off(0));
// Start lobby // Start lobby
lobby::serve(client, inbound, config, server, queue).await?; lobby::serve(client, client_info, inbound, config, server, queue).await?;
return Ok(()); return Ok(());
} }