Skip to content

Commit 26f650c

Browse files
committed
Support UDP Proxying
1 parent 01d710d commit 26f650c

9 files changed

Lines changed: 219 additions & 100 deletions

File tree

Cargo.lock

Lines changed: 20 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ ping-legacy = []
1111
anyhow = "1.0.100"
1212
async-trait = "0.1.89"
1313
bytes = "1.11.0"
14-
clap = { version = "4.5.52", features = ["derive"] }
14+
clap = { version = "4.5.53", features = ["derive"] }
1515
colored = "3.0.0"
1616
data-url = "0.3.2"
17+
fast-socks5 = "0.10.0"
1718
log = { version = "0.4.28", features = ["std"] }
1819
proxied = "1.1.0"
1920
regex = "1.12.2"

src/analyze/forge_info.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::analyze::{Analyzer, StatusPayload};
22
use crate::network::schema::{read_string, read_var_int_buf};
3+
use anyhow::{Result, anyhow};
34
use async_trait::async_trait;
45
use bytes::{Buf, BufMut, BytesMut};
56
use clap::Args;
6-
use std::io::ErrorKind;
77

88
#[derive(Args, Debug)]
99
pub struct ForgeInfoArgs {
@@ -16,13 +16,10 @@ pub struct ForgeInfo<'a> {
1616
args: &'a ForgeInfoArgs,
1717
}
1818

19-
async fn try_analyze_encoded(data: &str, display_channels: bool) -> std::io::Result<()> {
19+
async fn try_analyze_encoded(data: &str, display_channels: bool) -> Result<()> {
2020
let chars = data.encode_utf16().collect::<Vec<u16>>();
2121
if chars.len() < 2 {
22-
return Err(std::io::Error::new(
23-
ErrorKind::InvalidData,
24-
"ForgeData too short",
25-
));
22+
return Err(anyhow!("ForgeData too short"));
2623
}
2724

2825
let buffer_len = chars[0] as u32 | (chars[1] as u32) << 15;
@@ -132,7 +129,7 @@ impl Analyzer for ForgeInfo<'_> {
132129
if forge_data["truncated"].as_bool().unwrap_or(false) {
133130
log::info!("Server truncated mod information");
134131
}
135-
132+
136133
let default_vec = vec![];
137134
let mod_list = forge_data["mods"].as_array().unwrap_or(&default_vec);
138135
let ch_list = forge_data["channels"].as_array().unwrap_or(&default_vec);

src/mode/bedrock.rs

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
use crate::analyze::{MotdInfo, StatusPayload};
22
use crate::mode::QueryMode::BEDROCK;
33
use crate::mode::QueryModeHandler;
4-
use crate::network::resolve::{resolve_addr, sanitize_addr};
5-
use crate::network::util;
6-
use crate::network::util::io_timeout;
4+
use crate::network::connection::{ProxyableUdpSocket, UdpTarget, udp_socket};
5+
use crate::network::resolve::sanitize_addr;
6+
use crate::network::util::{generic_timeout, now_timestamp};
77
use anyhow::{Result, anyhow};
88
use async_trait::async_trait;
99
use bytes::{Buf, BufMut, BytesMut};
1010
use serde_json::json;
11-
use std::net::SocketAddr;
1211
use std::time::Duration;
13-
use tokio::net::UdpSocket;
1412
use tokio::task::JoinSet;
1513

1614
const MAGIC_HIGH: u64 = 0x00ffff00fefefefeu64;
1715
const MAGIC_LOW: u64 = 0xfdfdfdfd12345678u64;
1816

19-
async fn single_ip_check(addr: SocketAddr) -> Result<StatusPayload> {
17+
async fn single_ip_check(addr: &UdpTarget, socket: ProxyableUdpSocket) -> Result<StatusPayload> {
2018
let timeout_time = Duration::from_secs(5);
21-
let socket = UdpSocket::bind("0.0.0.0:0").await?;
22-
socket.connect(addr).await?;
23-
log::trace!("Connected to {}", addr);
2419

25-
let timestamp = util::now_timestamp();
20+
let timestamp = now_timestamp();
2621
let mut packet = Vec::from([1u8]);
2722
packet.put_i64(timestamp);
2823
packet.put_u64(MAGIC_HIGH);
@@ -32,10 +27,10 @@ async fn single_ip_check(addr: SocketAddr) -> Result<StatusPayload> {
3227
log::trace!("Sent Unconnected Ping packet");
3328

3429
let mut recv_buf = [0u8; 1024];
35-
let recv = io_timeout(timeout_time, socket.recv_from(&mut recv_buf), "Recv").await?;
30+
let recv = generic_timeout(timeout_time, socket.recv_from(&mut recv_buf), "Recv").await?;
3631
log::trace!("Received response from {}", addr);
3732

38-
let mut bytes = BytesMut::from(&recv_buf[..recv.0]);
33+
let mut bytes = BytesMut::from(&recv_buf[..recv]);
3934
if bytes.get_u8() != 0x1C {
4035
return Err(anyhow!("Unexpected response byte"));
4136
}
@@ -49,11 +44,11 @@ async fn single_ip_check(addr: SocketAddr) -> Result<StatusPayload> {
4944
if magic_high != MAGIC_HIGH || magic_low != MAGIC_LOW {
5045
return Err(anyhow!("Invalid magic number"));
5146
}
52-
if str_len as usize != recv.0 - 35 {
53-
return Err(anyhow!("Invalid string length",));
47+
if str_len as usize != recv - 35 {
48+
return Err(anyhow!("Invalid string length"));
5449
}
5550

56-
let ping = util::now_timestamp() - server_clock;
51+
let ping = now_timestamp() - server_clock;
5752
let resp = String::from_utf8(bytes.to_vec())?;
5853
log::trace!("Ping response: {}", resp);
5954

@@ -65,55 +60,47 @@ async fn single_ip_check(addr: SocketAddr) -> Result<StatusPayload> {
6560
Ok(StatusPayload {
6661
mode: BEDROCK,
6762
ping,
68-
max_players: Some(util::parse_i64(parts[5])?),
69-
player_count: Some(util::parse_i64(parts[4])?),
63+
max_players: Some(parts[5].parse()?),
64+
player_count: Some(parts[4].parse()?),
7065
players: None,
7166
motd: Some(MotdInfo::String(format!("{}\n{}", parts[1], parts[7]))),
72-
protocol: Some(util::parse_i64(parts[2])?),
67+
protocol: Some(parts[2].parse()?),
7368
version_name: Some(parts[3].to_string()),
7469
favicon: None,
7570
full_extra: Some(json!({"server_guid": server_guid, "game_mode": parts[8].to_string()})),
7671
})
7772
}
7873

79-
async fn safe_ip_check(addr: SocketAddr) -> Result<StatusPayload> {
80-
match single_ip_check(addr).await {
74+
async fn safe_ip_check(addr: UdpTarget, socket: ProxyableUdpSocket) -> Result<StatusPayload> {
75+
match single_ip_check(&addr, socket).await {
8176
Ok(status) => Ok(status),
82-
Err(e) => Err(anyhow!(
83-
"Protocol error in <{}:{}>: {}",
84-
addr.ip(),
85-
addr.port(),
86-
e
87-
)),
77+
Err(e) => Err(anyhow!("Protocol error in <{}>: {}", addr, e)),
8878
}
8979
}
9080

91-
async fn check_bedrock_server(addr_vec: Vec<SocketAddr>) -> Result<StatusPayload> {
92-
let mut set = JoinSet::new();
93-
94-
for addr in addr_vec {
95-
set.spawn(safe_ip_check(addr));
96-
}
97-
98-
while let Some(join_res) = set.join_next().await {
99-
if let Ok(res) = join_res {
100-
match res {
101-
Ok(res) => return Ok(res),
102-
Err(e) => log::warn!("{}", e),
103-
}
104-
}
105-
}
106-
107-
Err(anyhow!("No server found"))
108-
}
109-
11081
pub struct BedrockQuery;
11182

11283
#[async_trait]
11384
impl QueryModeHandler for BedrockQuery {
11485
async fn do_query(&self, addr: &str) -> Result<StatusPayload> {
11586
let (host, port) = sanitize_addr(addr, 19132)?;
116-
check_bedrock_server(resolve_addr(&host, port)).await
87+
let socks = udp_socket(&host, port, Duration::new(5, 0)).await?;
88+
let mut set = JoinSet::new();
89+
90+
for sock in socks {
91+
set.spawn(safe_ip_check(sock.0, sock.1));
92+
}
93+
94+
while let Some(join_res) = set.join_next().await {
95+
if let Ok(res) = join_res {
96+
match res {
97+
Ok(res) => return Ok(res),
98+
Err(e) => log::warn!("{}", e),
99+
}
100+
}
101+
}
102+
103+
Err(anyhow!("No server found"))
117104
}
118105
}
119106

src/mode/legacy.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use crate::analyze::*;
1+
use crate::analyze::{MotdInfo, StatusPayload};
22
use crate::mode::QueryMode::LEGACY;
33
use crate::mode::QueryModeHandler;
44
use crate::mode::java::JavaModeArgs;
55
use crate::network::connection::connect_tcp;
66
use crate::network::resolve::{resolve_server_srv, sanitize_addr};
7-
use crate::network::util;
87
use crate::network::util::{io_timeout, now_timestamp};
98
use anyhow::{Result, anyhow};
109
use async_trait::async_trait;
@@ -71,11 +70,11 @@ async fn single_ip_check(addr: &str, port: u16, stream: &mut TcpStream) -> Resul
7170
Ok(StatusPayload {
7271
mode: LEGACY,
7372
ping,
74-
max_players: Some(util::parse_i64(parts[5])?),
75-
player_count: Some(util::parse_i64(parts[4])?),
73+
max_players: Some(parts[5].parse()?),
74+
player_count: Some(parts[4].parse()?),
7675
players: None,
7776
motd: Some(MotdInfo::String(parts[3].to_string())),
78-
protocol: Some(util::parse_i64(parts[1])?),
77+
protocol: Some(parts[1].parse()?),
7978
version_name: Some(parts[2].to_string()),
8079
favicon: None,
8180
full_extra: Some(json!({"legacy_version": 1})),
@@ -93,8 +92,8 @@ async fn single_ip_check(addr: &str, port: u16, stream: &mut TcpStream) -> Resul
9392
Ok(StatusPayload {
9493
mode: LEGACY,
9594
ping,
96-
max_players: Some(util::parse_i64(parts[2])?),
97-
player_count: Some(util::parse_i64(parts[1])?),
95+
max_players: Some(parts[2].parse()?),
96+
player_count: Some(parts[1].parse()?),
9897
players: None,
9998
motd: Some(MotdInfo::String(parts[0].to_string())),
10099
protocol: None,

0 commit comments

Comments
 (0)