handshake_proxy/
handshake_proxy.rs1use std::{error::Error, sync::LazyLock};
5
6use azalea_protocol::{
7 connect::Connection,
8 packets::{
9 ClientIntention, PROTOCOL_VERSION, VERSION_NAME,
10 handshake::{
11 ClientboundHandshakePacket, ServerboundHandshakePacket,
12 s_intention::ServerboundIntention,
13 },
14 login::{ServerboundLoginPacket, s_hello::ServerboundHello},
15 status::{
16 ServerboundStatusPacket,
17 c_pong_response::ClientboundPongResponse,
18 c_status_response::{ClientboundStatusResponse, Players, Version},
19 },
20 },
21 read::ReadPacketError,
22};
23use futures::FutureExt;
24use tokio::{
25 io::{self, AsyncWriteExt},
26 net::{TcpListener, TcpStream},
27};
28use tracing::{Level, error, info, warn};
29
30const LISTEN_ADDR: &str = "127.0.0.1:25566";
31const PROXY_ADDR: &str = "127.0.0.1:25565";
32
33const PROXY_DESC: &str = "An Azalea Minecraft Proxy";
34
35static PROXY_FAVICON: LazyLock<Option<String>> = LazyLock::new(|| None);
37
38static PROXY_VERSION: LazyLock<Version> = LazyLock::new(|| Version {
39 name: VERSION_NAME.to_string(),
40 protocol: PROTOCOL_VERSION,
41});
42
43const PROXY_PLAYERS: Players = Players {
44 max: 1,
45 online: 0,
46 sample: Vec::new(),
47};
48
49const PROXY_SECURE_CHAT: Option<bool> = Some(false);
50
51#[tokio::main]
52async fn main() -> anyhow::Result<()> {
53 tracing_subscriber::fmt().with_max_level(Level::INFO).init();
54
55 let listener = TcpListener::bind(LISTEN_ADDR).await?;
57
58 info!("Listening on {LISTEN_ADDR}, proxying to {PROXY_ADDR}");
59
60 loop {
61 let (stream, _) = listener.accept().await?;
63 tokio::spawn(handle_connection(stream));
64 }
65}
66
67async fn handle_connection(stream: TcpStream) -> anyhow::Result<()> {
68 stream.set_nodelay(true)?;
69 let ip = stream.peer_addr()?;
70 let mut conn: Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> =
71 Connection::wrap(stream);
72
73 let intent = match conn.read().await {
77 Ok(packet) => match packet {
78 ServerboundHandshakePacket::Intention(packet) => {
79 info!(
80 "New connection from {}, hostname {:?}:{}, version {}, {:?}",
81 ip.ip(),
82 packet.hostname,
83 packet.port,
84 packet.protocol_version,
85 packet.intention
86 );
87 packet
88 }
89 },
90 Err(e) => {
91 let e = e.into();
92 warn!("Error during intent: {e}");
93 return Err(e);
94 }
95 };
96
97 match intent.intention {
98 ClientIntention::Status => {
101 let mut conn = conn.status();
102 loop {
103 match conn.read().await {
104 Ok(p) => match p {
105 ServerboundStatusPacket::StatusRequest(_) => {
106 conn.write(ClientboundStatusResponse {
107 description: PROXY_DESC.into(),
108 favicon: PROXY_FAVICON.clone(),
109 players: PROXY_PLAYERS.clone(),
110 version: PROXY_VERSION.clone(),
111 enforces_secure_chat: PROXY_SECURE_CHAT,
112 })
113 .await?;
114 }
115 ServerboundStatusPacket::PingRequest(p) => {
116 conn.write(ClientboundPongResponse { time: p.time }).await?;
117 break;
118 }
119 },
120 Err(e) => match *e {
121 ReadPacketError::ConnectionClosed => {
122 break;
123 }
124 e => {
125 warn!("Error during status: {e}");
126 return Err(e.into());
127 }
128 },
129 }
130 }
131 }
132 ClientIntention::Login => {
137 let mut conn = conn.login();
138 loop {
139 match conn.read().await {
140 Ok(p) => {
141 if let ServerboundLoginPacket::Hello(hello) = p {
142 info!(
143 "Player \'{0}\' from {1} logging in with uuid: {2}",
144 hello.name,
145 ip.ip(),
146 hello.profile_id.to_string()
147 );
148
149 tokio::spawn(transfer(conn.unwrap()?, intent, hello).map(|r| {
150 if let Err(e) = r {
151 error!("Failed to proxy: {e}");
152 }
153 }));
154
155 break;
156 }
157 }
158 Err(e) => match *e {
159 ReadPacketError::ConnectionClosed => {
160 break;
161 }
162 e => {
163 warn!("Error during login: {e}");
164 return Err(e.into());
165 }
166 },
167 }
168 }
169 }
170 ClientIntention::Transfer => {
171 warn!("Client attempted to join via transfer")
172 }
173 }
174
175 Ok(())
176}
177
178async fn transfer(
179 mut inbound: TcpStream,
180 intent: ServerboundIntention,
181 hello: ServerboundHello,
182) -> Result<(), Box<dyn Error>> {
183 let outbound = TcpStream::connect(PROXY_ADDR).await?;
184 let name = hello.name.clone();
185 outbound.set_nodelay(true)?;
186
187 let mut outbound_conn: Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> =
190 Connection::wrap(outbound);
191 outbound_conn.write(intent).await?;
192
193 let mut outbound_conn = outbound_conn.login();
194 outbound_conn.write(hello).await?;
195
196 let mut outbound = outbound_conn.unwrap()?;
197
198 let (mut ri, mut wi) = inbound.split();
201 let (mut ro, mut wo) = outbound.split();
202
203 let client_to_server = async {
204 io::copy(&mut ri, &mut wo).await?;
205 wo.shutdown().await
206 };
207
208 let server_to_client = async {
209 io::copy(&mut ro, &mut wi).await?;
210 wi.shutdown().await
211 };
212
213 tokio::try_join!(client_to_server, server_to_client)?;
214 info!("Player \'{name}\' left the game");
215
216 Ok(())
217}