mirror of
https://git.mentality.rip/numas13/xash3d-master.git
synced 2025-01-22 12:54:24 +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"
|
name = "xash3d-query"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
"getopts",
|
"getopts",
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -231,7 +231,7 @@ impl MasterServer {
|
|||||||
trace!("{}: recv {:?}", from, p);
|
trace!("{}: recv {:?}", from, p);
|
||||||
if p.filter.clver.map_or(false, |v| v < self.clver) {
|
if p.filter.clver.map_or(false, |v| v < self.clver) {
|
||||||
let iter = std::iter::once(self.update_addr);
|
let iter = std::iter::once(self.update_addr);
|
||||||
self.send_server_list(from, iter)?;
|
self.send_server_list(from, p.filter.key, iter)?;
|
||||||
} else {
|
} else {
|
||||||
let now = self.now();
|
let now = self.now();
|
||||||
let iter = self
|
let iter = self
|
||||||
@ -240,7 +240,7 @@ impl MasterServer {
|
|||||||
.filter(|i| i.1.is_valid(now, self.timeout.server))
|
.filter(|i| i.1.is_valid(now, self.timeout.server))
|
||||||
.filter(|i| i.1.matches(*i.0, p.region, &p.filter))
|
.filter(|i| i.1.matches(*i.0, p.region, &p.filter))
|
||||||
.map(|i| *i.0);
|
.map(|i| *i.0);
|
||||||
self.send_server_list(from, iter)?;
|
self.send_server_list(from, p.filter.key, iter)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
game::Packet::GetServerInfo(p) => {
|
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
|
where
|
||||||
A: ToSocketAddrs,
|
A: ToSocketAddrs,
|
||||||
I: Iterator<Item = SocketAddrV4>,
|
I: Iterator<Item = SocketAddrV4>,
|
||||||
{
|
{
|
||||||
let mut list = master::QueryServersResponse::new(iter);
|
let mut list = master::QueryServersResponse::new(iter, key);
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0; MAX_PACKET_SIZE];
|
let mut buf = [0; MAX_PACKET_SIZE];
|
||||||
let (n, is_end) = list.encode(&mut buf)?;
|
let (n, is_end) = list.encode(&mut buf)?;
|
||||||
|
@ -141,6 +141,7 @@ pub struct Filter<'a> {
|
|||||||
pub map: Option<Str<&'a [u8]>>,
|
pub map: Option<Str<&'a [u8]>>,
|
||||||
/// Client version.
|
/// Client version.
|
||||||
pub clver: Option<Version>,
|
pub clver: Option<Version>,
|
||||||
|
pub key: Option<u32>,
|
||||||
|
|
||||||
pub flags: FilterFlags,
|
pub flags: FilterFlags,
|
||||||
pub flags_mask: 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"nat" => filter.insert_flag(FilterFlags::NAT, cur.get_key_value()?),
|
||||||
b"lan" => filter.insert_flag(FilterFlags::LAN, 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"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
|
// skip unknown fields
|
||||||
let value = Str(cur.get_key_value_raw()?);
|
let value = Str(cur.get_key_value_raw()?);
|
||||||
@ -234,6 +242,9 @@ impl fmt::Display for &Filter<'_> {
|
|||||||
display_flag!("nat", FilterFlags::NAT);
|
display_flag!("nat", FilterFlags::NAT);
|
||||||
display_flag!("lan", FilterFlags::LAN);
|
display_flag!("lan", FilterFlags::LAN);
|
||||||
display_flag!("bots", FilterFlags::BOTS);
|
display_flag!("bots", FilterFlags::BOTS);
|
||||||
|
if let Some(x) = self.key {
|
||||||
|
write!(fmt, "\\key\\{:x}", x)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ mod tests {
|
|||||||
filter: Filter {
|
filter: Filter {
|
||||||
gamedir: Some(Str(&b"valve"[..])),
|
gamedir: Some(Str(&b"valve"[..])),
|
||||||
map: Some(Str(&b"crossfire"[..])),
|
map: Some(Str(&b"crossfire"[..])),
|
||||||
|
key: Some(0xdeadbeef),
|
||||||
clver: Some(Version::new(0, 20)),
|
clver: Some(Version::new(0, 20)),
|
||||||
flags: FilterFlags::all(),
|
flags: FilterFlags::all(),
|
||||||
flags_mask: FilterFlags::all(),
|
flags_mask: FilterFlags::all(),
|
||||||
|
@ -46,6 +46,7 @@ impl ChallengeResponse {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct QueryServersResponse<I> {
|
pub struct QueryServersResponse<I> {
|
||||||
inner: I,
|
inner: I,
|
||||||
|
pub key: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryServersResponse<()> {
|
impl QueryServersResponse<()> {
|
||||||
@ -60,12 +61,20 @@ impl<'a> QueryServersResponse<&'a [u8]> {
|
|||||||
return Err(Error::InvalidPacket);
|
return Err(Error::InvalidPacket);
|
||||||
}
|
}
|
||||||
let s = cur.get_bytes(cur.remaining())?;
|
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]) {
|
let inner = if s.ends_with(&[0; 6]) {
|
||||||
&s[..s.len() - 6]
|
&s[..s.len() - 6]
|
||||||
} else {
|
} else {
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
Ok(Self { inner })
|
Ok(Self { inner, key })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl 'a + Iterator<Item = SocketAddrV4> {
|
pub fn iter(&self) -> impl 'a + Iterator<Item = SocketAddrV4> {
|
||||||
@ -86,13 +95,18 @@ impl<I> QueryServersResponse<I>
|
|||||||
where
|
where
|
||||||
I: Iterator<Item = SocketAddrV4>,
|
I: Iterator<Item = SocketAddrV4>,
|
||||||
{
|
{
|
||||||
pub fn new(iter: I) -> Self {
|
pub fn new(iter: I, key: Option<u32>) -> Self {
|
||||||
Self { inner: iter }
|
Self { inner: iter, key }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> {
|
pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> {
|
||||||
let mut cur = CursorMut::new(buf);
|
let mut cur = CursorMut::new(buf);
|
||||||
cur.put_bytes(QueryServersResponse::HEADER)?;
|
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;
|
let mut is_end = false;
|
||||||
while cur.remaining() >= 12 {
|
while cur.remaining() >= 12 {
|
||||||
match self.inner.next() {
|
match self.inner.next() {
|
||||||
@ -191,7 +205,7 @@ mod tests {
|
|||||||
"1.2.3.4:27003".parse().unwrap(),
|
"1.2.3.4:27003".parse().unwrap(),
|
||||||
"1.2.3.4:27004".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 mut buf = [0; 512];
|
||||||
let (n, _) = p.encode(&mut buf).unwrap();
|
let (n, _) = p.encode(&mut buf).unwrap();
|
||||||
let e = QueryServersResponse::decode(&buf[..n]).unwrap();
|
let e = QueryServersResponse::decode(&buf[..n]).unwrap();
|
||||||
|
@ -14,6 +14,7 @@ color = ["termion"]
|
|||||||
libc = "0.2.148"
|
libc = "0.2.148"
|
||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
getopts = "0.2.21"
|
getopts = "0.2.21"
|
||||||
|
fastrand = "2.0.1"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
termion = { version = "2", optional = true }
|
termion = { version = "2", optional = true }
|
||||||
|
@ -25,6 +25,7 @@ pub struct Cli {
|
|||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub force_color: bool,
|
pub force_color: bool,
|
||||||
pub filter: String,
|
pub filter: String,
|
||||||
|
pub key: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cli {
|
impl Default for Cli {
|
||||||
@ -43,6 +44,7 @@ impl Default for Cli {
|
|||||||
force_color: false,
|
force_color: false,
|
||||||
// if changed do not forget to update cli parsing
|
// if changed do not forget to update cli parsing
|
||||||
filter: format!("\\gamedir\\valve\\clver\\{}", proto::CLIENT_VERSION),
|
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("j", "json", "output JSON");
|
||||||
opts.optflag("d", "debug", "output debug");
|
opts.optflag("d", "debug", "output debug");
|
||||||
opts.optflag("F", "force-color", "force colored output");
|
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);
|
let help = format!("query filter [default: {:?}]", cli.filter);
|
||||||
opts.optopt("f", "filter", &help, "FILTER");
|
opts.optopt("f", "filter", &help, "FILTER");
|
||||||
|
|
||||||
@ -180,6 +183,12 @@ pub fn parse() -> Cli {
|
|||||||
cli.filter = filter;
|
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.json = matches.opt_present("json");
|
||||||
cli.debug = matches.opt_present("debug");
|
cli.debug = matches.opt_present("debug");
|
||||||
cli.force_color = matches.opt_present("force-color");
|
cli.force_color = matches.opt_present("force-color");
|
||||||
|
@ -36,8 +36,13 @@ enum ServerResultKind {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
info: ServerInfo,
|
info: ServerInfo,
|
||||||
},
|
},
|
||||||
Error { message: String },
|
Error {
|
||||||
Invalid { message: String, response: String },
|
message: String,
|
||||||
|
},
|
||||||
|
Invalid {
|
||||||
|
message: String,
|
||||||
|
response: String,
|
||||||
|
},
|
||||||
Timeout,
|
Timeout,
|
||||||
Protocol,
|
Protocol,
|
||||||
}
|
}
|
||||||
@ -298,7 +303,9 @@ impl<'a> Scan<'a> {
|
|||||||
|
|
||||||
if self.is_master(&from) {
|
if self.is_master(&from) {
|
||||||
if let Ok(packet) = master::QueryServersResponse::decode(&buf[..n]) {
|
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 {
|
} else {
|
||||||
eprintln!("warn: invalid packet from master {}", from);
|
eprintln!("warn: invalid packet from master {}", from);
|
||||||
}
|
}
|
||||||
@ -367,19 +374,22 @@ impl<'a> Scan<'a> {
|
|||||||
|
|
||||||
if self.is_master(&from) {
|
if self.is_master(&from) {
|
||||||
if let Ok(packet) = master::QueryServersResponse::decode(raw) {
|
if let Ok(packet) = master::QueryServersResponse::decode(raw) {
|
||||||
for addr in packet.iter().filter(|i| set.insert(*i)) {
|
if self.check_key(&from, packet.key) {
|
||||||
let mut buf = [0; 512];
|
for addr in packet.iter().filter(|i| set.insert(*i)) {
|
||||||
let n = game::GetServerInfo::new(self.cli.protocol[0]).encode(&mut buf)?;
|
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) {
|
match self.sock.send_to(&buf[..n], addr) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let query = ServerQuery::new(0);
|
let query = ServerQuery::new(0);
|
||||||
server_end = query.start + server_timeout;
|
server_end = query.start + server_timeout;
|
||||||
active.insert(addr, query);
|
active.insert(addr, query);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let res = ServerResult::error(addr, e);
|
let res = ServerResult::error(addr, e);
|
||||||
out.insert(addr, res);
|
out.insert(addr, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,6 +436,18 @@ impl<'a> Scan<'a> {
|
|||||||
|
|
||||||
Ok(out)
|
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> {
|
fn query_server_info(cli: &Cli, servers: &[String]) -> Result<(), Error> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user