aboutsummaryrefslogtreecommitdiff
path: root/azalea-protocol/examples
diff options
context:
space:
mode:
authorEightFactorial <murphkev000@gmail.com>2023-01-21 20:14:23 -0800
committerGitHub <noreply@github.com>2023-01-21 22:14:23 -0600
commit9ee5e71bb13e596248fde000d8717c86276b0ce1 (patch)
treebd6363af53bca9bbd3dede1c7ee59615b94eb107 /azalea-protocol/examples
parent1059afa6fcf8b2776fd25dac07ed2e76ab48bed3 (diff)
downloadazalea-drasl-9ee5e71bb13e596248fde000d8717c86276b0ce1.tar.xz
Server functions and proxy example (#59)
* A couple useful things for servers * Add proxy example * Use Uuid's serde feature * Add const options to proxy example * Example crates go in dev-dependencies * Warn instead of error * Log address on login * Requested changes * add a test for deserializing game profile + random small changes Co-authored-by: mat <github@matdoes.dev>
Diffstat (limited to 'azalea-protocol/examples')
-rw-r--r--azalea-protocol/examples/handshake_proxy.rs225
1 files changed, 225 insertions, 0 deletions
diff --git a/azalea-protocol/examples/handshake_proxy.rs b/azalea-protocol/examples/handshake_proxy.rs
new file mode 100644
index 00000000..5a4b4b6a
--- /dev/null
+++ b/azalea-protocol/examples/handshake_proxy.rs
@@ -0,0 +1,225 @@
+//! A "simple" server that gets login information and proxies connections.
+//! After login all connections are encrypted and Azalea cannot read them.
+
+use azalea_protocol::{
+ connect::Connection,
+ packets::{
+ handshake::{
+ client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
+ ServerboundHandshakePacket,
+ },
+ login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket},
+ status::{
+ clientbound_pong_response_packet::ClientboundPongResponsePacket,
+ clientbound_status_response_packet::{
+ ClientboundStatusResponsePacket, Players, Version,
+ },
+ ServerboundStatusPacket,
+ },
+ ConnectionProtocol, PROTOCOL_VERSION,
+ },
+ read::ReadPacketError,
+};
+use futures::FutureExt;
+use log::{error, info, warn};
+use once_cell::sync::Lazy;
+use std::error::Error;
+use tokio::{
+ io::{self, AsyncWriteExt},
+ net::{TcpListener, TcpStream},
+};
+use tracing::Level;
+
+const LISTEN_ADDR: &str = "127.0.0.1:25566";
+const PROXY_ADDR: &str = "173.205.80.60:25565";
+
+const PROXY_DESC: &str = "An Azalea Minecraft Proxy";
+
+// String must be formatted like "data:image/png;base64,<data>"
+static PROXY_FAVICON: Lazy<Option<String>> = Lazy::new(|| None);
+
+static PROXY_VERSION: Lazy<Version> = Lazy::new(|| Version {
+ name: "1.19.3".to_string(),
+ protocol: PROTOCOL_VERSION as i32,
+});
+
+const PROXY_PLAYERS: Players = Players {
+ max: 1,
+ online: 0,
+ sample: Vec::new(),
+};
+
+const PROXY_PREVIEWS_CHAT: Option<bool> = Some(false);
+const PROXY_SECURE_CHAT: Option<bool> = Some(false);
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+ tracing_subscriber::fmt().with_max_level(Level::INFO).init();
+
+ // Bind to an address and port
+ let listener = TcpListener::bind(LISTEN_ADDR).await?;
+ loop {
+ // When a connection is made, pass it off to another thread
+ let (stream, _) = listener.accept().await?;
+ tokio::spawn(handle_connection(stream));
+ }
+}
+
+async fn handle_connection(stream: TcpStream) -> anyhow::Result<()> {
+ stream.set_nodelay(true)?;
+ let ip = stream.peer_addr()?;
+ let mut conn: Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> =
+ Connection::wrap(stream);
+
+ // The first packet sent from a client is the intent packet.
+ // This specifies whether the client is pinging
+ // the server or is going to join the game.
+ let intent = match conn.read().await {
+ Ok(packet) => match packet {
+ ServerboundHandshakePacket::ClientIntention(packet) => {
+ info!(
+ "New connection: {0}, Version {1}, {2:?}",
+ ip.ip(),
+ packet.protocol_version,
+ packet.intention
+ );
+ packet
+ }
+ },
+ Err(e) => {
+ let e = e.into();
+ warn!("Error during intent: {e}");
+ return Err(e);
+ }
+ };
+
+ match intent.intention {
+ // If the client is pinging the proxy,
+ // reply with the information below.
+ ConnectionProtocol::Status => {
+ let mut conn = conn.status();
+ loop {
+ match conn.read().await {
+ Ok(p) => match p {
+ ServerboundStatusPacket::StatusRequest(_) => {
+ conn.write(
+ ClientboundStatusResponsePacket {
+ description: PROXY_DESC.into(),
+ favicon: PROXY_FAVICON.clone(),
+ players: PROXY_PLAYERS.clone(),
+ version: PROXY_VERSION.clone(),
+ previews_chat: PROXY_PREVIEWS_CHAT,
+ enforces_secure_chat: PROXY_SECURE_CHAT,
+ }
+ .get(),
+ )
+ .await?;
+ }
+ ServerboundStatusPacket::PingRequest(p) => {
+ conn.write(ClientboundPongResponsePacket { time: p.time }.get())
+ .await?;
+ break;
+ }
+ },
+ Err(e) => match *e {
+ ReadPacketError::ConnectionClosed => {
+ break;
+ }
+ e => {
+ warn!("Error during status: {e}");
+ return Err(e.into());
+ }
+ },
+ }
+ }
+ }
+ // If the client intends to join the proxy,
+ // wait for them to send the `Hello` packet to
+ // log their username and uuid, then forward the
+ // connection along to the proxy target.
+ ConnectionProtocol::Login => {
+ let mut conn = conn.login();
+ loop {
+ match conn.read().await {
+ Ok(p) => {
+ if let ServerboundLoginPacket::Hello(hello) = p {
+ info!(
+ "Player \'{0}\' from {1} logging in with uuid: {2}",
+ hello.name,
+ ip.ip(),
+ if let Some(id) = hello.profile_id {
+ id.to_string()
+ } else {
+ "".to_string()
+ }
+ );
+
+ tokio::spawn(transfer(conn.unwrap()?, intent, hello).map(|r| {
+ if let Err(e) = r {
+ error!("Failed to proxy: {e}");
+ }
+ }));
+
+ break;
+ }
+ }
+ Err(e) => match *e {
+ ReadPacketError::ConnectionClosed => {
+ break;
+ }
+ e => {
+ warn!("Error during login: {e}");
+ return Err(e.into());
+ }
+ },
+ }
+ }
+ }
+ _ => {
+ warn!("Client provided weird intent: {:?}", intent.intention);
+ }
+ }
+
+ Ok(())
+}
+
+async fn transfer(
+ mut inbound: TcpStream,
+ intent: ClientIntentionPacket,
+ hello: ServerboundHelloPacket,
+) -> Result<(), Box<dyn Error>> {
+ let outbound = TcpStream::connect(PROXY_ADDR).await?;
+ let name = hello.name.clone();
+ outbound.set_nodelay(true)?;
+
+ // Repeat the intent and hello packet
+ // recieved earlier to the proxy target
+ let mut outbound_conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> =
+ Connection::wrap(outbound);
+ outbound_conn.write(intent.get()).await?;
+
+ let mut outbound_conn = outbound_conn.login();
+ outbound_conn.write(hello.get()).await?;
+
+ let mut outbound = outbound_conn.unwrap()?;
+
+ // Split the incoming and outgoing connections in
+ // halves and handle each pair on separate threads.
+ let (mut ri, mut wi) = inbound.split();
+ let (mut ro, mut wo) = outbound.split();
+
+ let client_to_server = async {
+ io::copy(&mut ri, &mut wo).await?;
+ wo.shutdown().await
+ };
+
+ let server_to_client = async {
+ io::copy(&mut ro, &mut wi).await?;
+ wi.shutdown().await
+ };
+
+ tokio::try_join!(client_to_server, server_to_client)?;
+ info!("Player \'{name}\' left the game");
+
+ Ok(())
+}