mirror of
https://git.mentality.rip/numas13/xash3d-master.git
synced 2025-01-22 04:44:31 +00:00
protocol: add challenge key to game requests
This commit is contained in:
parent
c3fe66441f
commit
e6b1d866eb
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -509,6 +509,7 @@ dependencies = [
|
||||
name = "xash3d-query"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getopts",
|
||||
"libc",
|
||||
"serde",
|
||||
|
@ -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)?;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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 }
|
||||
|
@ -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");
|
||||
|
@ -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> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user