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> {