protocol: add challenge key to game requests

This commit is contained in:
Denis Drakhnia 2023-10-22 17:49:36 +03:00
parent c3fe66441f
commit e6b1d866eb
8 changed files with 82 additions and 23 deletions

1
Cargo.lock generated
View File

@ -509,6 +509,7 @@ dependencies = [
name = "xash3d-query"
version = "0.1.0"
dependencies = [
"fastrand",
"getopts",
"libc",
"serde",

View File

@ -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<A, I>(&self, to: A, iter: I) -> Result<(), Error>
fn send_server_list<A, I>(&self, to: A, key: Option<u32>, iter: I) -> Result<(), Error>
where
A: ToSocketAddrs,
I: Iterator<Item = SocketAddrV4>,
{
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)?;

View File

@ -141,6 +141,7 @@ pub struct Filter<'a> {
pub map: Option<Str<&'a [u8]>>,
/// Client version.
pub clver: Option<Version>,
pub key: Option<u32>,
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(())
}

View File

@ -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(),

View File

@ -46,6 +46,7 @@ impl ChallengeResponse {
#[derive(Clone, Debug, PartialEq)]
pub struct QueryServersResponse<I> {
inner: I,
pub key: Option<u32>,
}
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<Item = SocketAddrV4> {
@ -86,13 +95,18 @@ impl<I> QueryServersResponse<I>
where
I: Iterator<Item = SocketAddrV4>,
{
pub fn new(iter: I) -> Self {
Self { inner: iter }
pub fn new(iter: I, key: Option<u32>) -> 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();

View File

@ -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 }

View File

@ -25,6 +25,7 @@ pub struct Cli {
pub debug: bool,
pub force_color: bool,
pub filter: String,
pub key: Option<u32>,
}
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");

View File

@ -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)?;
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);
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<u32>) -> 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> {