From e6b1d866eb6890967c9dcea9a6077c605424b002 Mon Sep 17 00:00:00 2001 From: Denis Drakhnia Date: Sun, 22 Oct 2023 17:49:36 +0300 Subject: [PATCH] protocol: add challenge key to game requests --- Cargo.lock | 1 + master/src/master_server.rs | 8 +++--- protocol/src/filter.rs | 11 ++++++++ protocol/src/game.rs | 1 + protocol/src/master.rs | 22 ++++++++++++--- query/Cargo.toml | 1 + query/src/cli.rs | 9 +++++++ query/src/main.rs | 54 ++++++++++++++++++++++++++----------- 8 files changed, 83 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c4624b..49453ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ dependencies = [ name = "xash3d-query" version = "0.1.0" dependencies = [ + "fastrand", "getopts", "libc", "serde", diff --git a/master/src/master_server.rs b/master/src/master_server.rs index 723cfc5..bbf604b 100644 --- a/master/src/master_server.rs +++ b/master/src/master_server.rs @@ -231,7 +231,7 @@ impl MasterServer { trace!("{}: recv {:?}", from, p); if p.filter.clver.map_or(false, |v| v < self.clver) { let iter = std::iter::once(self.update_addr); - self.send_server_list(from, iter)?; + self.send_server_list(from, p.filter.key, iter)?; } else { let now = self.now(); let iter = self @@ -240,7 +240,7 @@ impl MasterServer { .filter(|i| i.1.is_valid(now, self.timeout.server)) .filter(|i| i.1.matches(*i.0, p.region, &p.filter)) .map(|i| *i.0); - self.send_server_list(from, iter)?; + self.send_server_list(from, p.filter.key, iter)?; } } game::Packet::GetServerInfo(p) => { @@ -399,12 +399,12 @@ impl MasterServer { } } - fn send_server_list(&self, to: A, iter: I) -> Result<(), Error> + fn send_server_list(&self, to: A, key: Option, iter: I) -> Result<(), Error> where A: ToSocketAddrs, I: Iterator, { - let mut list = master::QueryServersResponse::new(iter); + let mut list = master::QueryServersResponse::new(iter, key); loop { let mut buf = [0; MAX_PACKET_SIZE]; let (n, is_end) = list.encode(&mut buf)?; diff --git a/protocol/src/filter.rs b/protocol/src/filter.rs index e7449fd..63db85d 100644 --- a/protocol/src/filter.rs +++ b/protocol/src/filter.rs @@ -141,6 +141,7 @@ pub struct Filter<'a> { pub map: Option>, /// Client version. pub clver: Option, + pub key: Option, pub flags: FilterFlags, pub flags_mask: FilterFlags, @@ -192,6 +193,13 @@ impl<'a> TryFrom<&'a [u8]> for Filter<'a> { b"nat" => filter.insert_flag(FilterFlags::NAT, cur.get_key_value()?), b"lan" => filter.insert_flag(FilterFlags::LAN, cur.get_key_value()?), b"bots" => filter.insert_flag(FilterFlags::BOTS, cur.get_key_value()?), + b"key" => { + filter.key = { + let s = cur.get_key_value::<&str>()?; + let x = u32::from_str_radix(s, 16).map_err(|_| Error::InvalidPacket)?; + Some(x) + } + } _ => { // skip unknown fields let value = Str(cur.get_key_value_raw()?); @@ -234,6 +242,9 @@ impl fmt::Display for &Filter<'_> { display_flag!("nat", FilterFlags::NAT); display_flag!("lan", FilterFlags::LAN); display_flag!("bots", FilterFlags::BOTS); + if let Some(x) = self.key { + write!(fmt, "\\key\\{:x}", x)?; + } Ok(()) } diff --git a/protocol/src/game.rs b/protocol/src/game.rs index b57c134..9089021 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -120,6 +120,7 @@ mod tests { filter: Filter { gamedir: Some(Str(&b"valve"[..])), map: Some(Str(&b"crossfire"[..])), + key: Some(0xdeadbeef), clver: Some(Version::new(0, 20)), flags: FilterFlags::all(), flags_mask: FilterFlags::all(), diff --git a/protocol/src/master.rs b/protocol/src/master.rs index bba4a65..d5bdac2 100644 --- a/protocol/src/master.rs +++ b/protocol/src/master.rs @@ -46,6 +46,7 @@ impl ChallengeResponse { #[derive(Clone, Debug, PartialEq)] pub struct QueryServersResponse { inner: I, + pub key: Option, } impl QueryServersResponse<()> { @@ -60,12 +61,20 @@ impl<'a> QueryServersResponse<&'a [u8]> { return Err(Error::InvalidPacket); } let s = cur.get_bytes(cur.remaining())?; + + // extra header for key sent in QueryServers packet + let (s, key) = if s.len() >= 6 && s[0] == 0x7f { + (&s[6..], Some(u32::from_le_bytes([s[1], s[2], s[3], s[4]]))) + } else { + (s, None) + }; + let inner = if s.ends_with(&[0; 6]) { &s[..s.len() - 6] } else { s }; - Ok(Self { inner }) + Ok(Self { inner, key }) } pub fn iter(&self) -> impl 'a + Iterator { @@ -86,13 +95,18 @@ impl QueryServersResponse where I: Iterator, { - pub fn new(iter: I) -> Self { - Self { inner: iter } + pub fn new(iter: I, key: Option) -> Self { + Self { inner: iter, key } } pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> { let mut cur = CursorMut::new(buf); cur.put_bytes(QueryServersResponse::HEADER)?; + if let Some(key) = self.key { + cur.put_u8(0x7f)?; + cur.put_u32_le(key)?; + cur.put_u8(8)?; + } let mut is_end = false; while cur.remaining() >= 12 { match self.inner.next() { @@ -191,7 +205,7 @@ mod tests { "1.2.3.4:27003".parse().unwrap(), "1.2.3.4:27004".parse().unwrap(), ]; - let mut p = QueryServersResponse::new(servers.iter().cloned()); + let mut p = QueryServersResponse::new(servers.iter().cloned(), Some(0xdeadbeef)); let mut buf = [0; 512]; let (n, _) = p.encode(&mut buf).unwrap(); let e = QueryServersResponse::decode(&buf[..n]).unwrap(); diff --git a/query/Cargo.toml b/query/Cargo.toml index e1ec87e..b6e43d1 100644 --- a/query/Cargo.toml +++ b/query/Cargo.toml @@ -14,6 +14,7 @@ color = ["termion"] libc = "0.2.148" thiserror = "1.0.49" getopts = "0.2.21" +fastrand = "2.0.1" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" termion = { version = "2", optional = true } diff --git a/query/src/cli.rs b/query/src/cli.rs index d87546c..4f94783 100644 --- a/query/src/cli.rs +++ b/query/src/cli.rs @@ -25,6 +25,7 @@ pub struct Cli { pub debug: bool, pub force_color: bool, pub filter: String, + pub key: Option, } impl Default for Cli { @@ -43,6 +44,7 @@ impl Default for Cli { force_color: false, // if changed do not forget to update cli parsing filter: format!("\\gamedir\\valve\\clver\\{}", proto::CLIENT_VERSION), + key: None, } } } @@ -99,6 +101,7 @@ pub fn parse() -> Cli { opts.optflag("j", "json", "output JSON"); opts.optflag("d", "debug", "output debug"); opts.optflag("F", "force-color", "force colored output"); + opts.optflag("k", "key", "send challenge key to master"); let help = format!("query filter [default: {:?}]", cli.filter); opts.optopt("f", "filter", &help, "FILTER"); @@ -180,6 +183,12 @@ pub fn parse() -> Cli { cli.filter = filter; } + if matches.opt_present("key") { + let key = fastrand::u32(..); + cli.key = Some(key); + cli.filter.push_str(&format!("\\key\\{:x}", key)); + } + cli.json = matches.opt_present("json"); cli.debug = matches.opt_present("debug"); cli.force_color = matches.opt_present("force-color"); diff --git a/query/src/main.rs b/query/src/main.rs index 373bbfc..4f5e53e 100644 --- a/query/src/main.rs +++ b/query/src/main.rs @@ -36,8 +36,13 @@ enum ServerResultKind { #[serde(flatten)] info: ServerInfo, }, - Error { message: String }, - Invalid { message: String, response: String }, + Error { + message: String, + }, + Invalid { + message: String, + response: String, + }, Timeout, Protocol, } @@ -298,7 +303,9 @@ impl<'a> Scan<'a> { if self.is_master(&from) { if let Ok(packet) = master::QueryServersResponse::decode(&buf[..n]) { - set.extend(packet.iter()); + if self.check_key(&from, packet.key) { + set.extend(packet.iter()); + } } else { eprintln!("warn: invalid packet from master {}", from); } @@ -367,19 +374,22 @@ impl<'a> Scan<'a> { if self.is_master(&from) { if let Ok(packet) = master::QueryServersResponse::decode(raw) { - for addr in packet.iter().filter(|i| set.insert(*i)) { - let mut buf = [0; 512]; - let n = game::GetServerInfo::new(self.cli.protocol[0]).encode(&mut buf)?; - - match self.sock.send_to(&buf[..n], addr) { - Ok(_) => { - let query = ServerQuery::new(0); - server_end = query.start + server_timeout; - active.insert(addr, query); - } - Err(e) => { - let res = ServerResult::error(addr, e); - out.insert(addr, res); + if self.check_key(&from, packet.key) { + for addr in packet.iter().filter(|i| set.insert(*i)) { + let mut buf = [0; 512]; + let n = + game::GetServerInfo::new(self.cli.protocol[0]).encode(&mut buf)?; + + match self.sock.send_to(&buf[..n], addr) { + Ok(_) => { + let query = ServerQuery::new(0); + server_end = query.start + server_timeout; + active.insert(addr, query); + } + Err(e) => { + let res = ServerResult::error(addr, e); + out.insert(addr, res); + } } } } @@ -426,6 +436,18 @@ impl<'a> Scan<'a> { Ok(out) } + + fn check_key(&self, from: &SocketAddrV4, key: Option) -> bool { + let res = match (self.cli.key, key) { + (Some(a), Some(b)) => a == b, + (None, None) => true, + _ => false, + }; + if !res { + eprintln!("error: invalid key from master({})", from); + } + res + } } fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {