Browse Source

Add update client message

ipv6
Denis Drakhnia 1 year ago
parent
commit
ed83a77b3b
  1. 7
      config/main.toml
  2. 2
      src/client.rs
  3. 32
      src/config.rs
  4. 48
      src/filter.rs
  5. 96
      src/master_server.rs
  6. 7
      src/parser.rs

7
config/main.toml

@ -13,3 +13,10 @@ port = 27010
challenge = 300 challenge = 300
# Time in seconds while server is valid # Time in seconds while server is valid
server = 300 server = 300
[client]
# If client version is less then show update message
version = "0.20"
update_title = "https://github.com/FWGS/xash3d-fwgs"
update_map = "Update please"
update_addr = "127.0.0.1:27010"

2
src/client.rs

@ -41,6 +41,7 @@ pub enum Packet<'a> {
ServerAdd(Option<u32>, ServerInfo<&'a str>), ServerAdd(Option<u32>, ServerInfo<&'a str>),
ServerRemove, ServerRemove,
QueryServers(Region, Filter<'a>), QueryServers(Region, Filter<'a>),
ServerInfo,
} }
impl<'a> Packet<'a> { impl<'a> Packet<'a> {
@ -70,6 +71,7 @@ impl<'a> Packet<'a> {
} }
[b'b', b'\n'] => Ok(Self::ServerRemove), [b'b', b'\n'] => Ok(Self::ServerRemove),
[b'q'] => Ok(Self::Challenge(None)), [b'q'] => Ok(Self::Challenge(None)),
[0xff, 0xff, 0xff, 0xff, b'i', b'n', b'f', b'o', b' ', _, _] => Ok(Self::ServerInfo),
_ => Err(Error::InvalidPacket), _ => Err(Error::InvalidPacket),
} }
} }

32
src/config.rs

@ -3,13 +3,15 @@
use std::fs; use std::fs;
use std::io; use std::io;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
use std::path::Path; use std::path::Path;
use log::LevelFilter; use log::LevelFilter;
use serde::{de::Error as _, Deserialize, Deserializer}; use serde::{de::Error as _, Deserialize, Deserializer};
use thiserror::Error; use thiserror::Error;
use crate::filter::Version;
pub const DEFAULT_CONFIG_PATH: &str = "config/main.toml"; pub const DEFAULT_CONFIG_PATH: &str = "config/main.toml";
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
@ -31,6 +33,8 @@ pub struct Config {
pub log: LogConfig, pub log: LogConfig,
#[serde(default)] #[serde(default)]
pub server: ServerConfig, pub server: ServerConfig,
#[serde(default)]
pub client: ClientConfig,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -88,6 +92,20 @@ impl Default for TimeoutConfig {
} }
} }
#[derive(Deserialize, Default, Debug)]
#[serde(deny_unknown_fields)]
pub struct ClientConfig {
#[serde(default)]
#[serde(deserialize_with = "deserialize_version")]
pub version: Version,
#[serde(default)]
pub update_map: Box<str>,
#[serde(default)]
pub update_title: Box<str>,
#[serde(default)]
pub update_addr: Option<SocketAddrV4>,
}
fn default_log_level() -> LevelFilter { fn default_log_level() -> LevelFilter {
LevelFilter::Warn LevelFilter::Warn
} }
@ -109,8 +127,7 @@ where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let s = <&str>::deserialize(de)?; let s = <&str>::deserialize(de)?;
parse_log_level(s) parse_log_level(s).ok_or_else(|| D::Error::custom(format!("Invalid log level: \"{}\"", s)))
.ok_or_else(|| D::Error::custom(format!("Invalid value for log option: \"{}\"", s)))
} }
pub fn parse_log_level(s: &str) -> Option<LevelFilter> { pub fn parse_log_level(s: &str) -> Option<LevelFilter> {
@ -136,6 +153,15 @@ pub fn parse_log_level(s: &str) -> Option<LevelFilter> {
Some(level_filter) Some(level_filter)
} }
fn deserialize_version<'de, D>(de: D) -> Result<Version, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(de)?;
s.parse()
.map_err(|_| D::Error::custom(format!("Invalid version: \"{}\"", s)))
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, Error> { pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
let data = fs::read(path)?; let data = fs::read(path)?;
Ok(toml::de::from_slice(&data)?) Ok(toml::de::from_slice(&data)?)

48
src/filter.rs

@ -28,7 +28,10 @@
//! * Do not have bots //! * Do not have bots
//! * Is not protected by a password //! * Is not protected by a password
use std::fmt;
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use std::num::ParseIntError;
use std::str::FromStr;
use bitflags::bitflags; use bitflags::bitflags;
use log::{debug, log_enabled, Level}; use log::{debug, log_enabled, Level};
@ -79,6 +82,49 @@ impl<T> From<&ServerInfo<T>> for FilterFlags {
} }
} }
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version {
pub major: u8,
pub minor: u8,
}
impl Version {
pub fn new(major: u8, minor: u8) -> Self {
Self { major, minor }
}
}
impl fmt::Debug for Version {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}.{}", self.major, self.minor)
}
}
impl fmt::Display for Version {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}.{}", self.major, self.minor)
}
}
impl FromStr for Version {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (major, minor) = s.split_once('.').unwrap_or((s, "0"));
Ok(Self::new(major.parse()?, minor.parse()?))
}
}
impl ParseValue<'_> for Version {
type Err = ParserError;
fn parse(p: &mut Parser<'_>) -> Result<Self, Self::Err> {
let s = p.parse::<&str>()?;
let v = s.parse()?;
Ok(v)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Filter<'a> { pub struct Filter<'a> {
/// Servers running the specified modification (ex. cstrike) /// Servers running the specified modification (ex. cstrike)
@ -92,7 +138,7 @@ pub struct Filter<'a> {
/// Return only servers on the specified IP address (port supported and optional) /// Return only servers on the specified IP address (port supported and optional)
pub gameaddr: Option<SocketAddrV4>, pub gameaddr: Option<SocketAddrV4>,
/// Client version. /// Client version.
pub clver: Option<&'a str>, pub clver: Option<Version>,
pub flags: FilterFlags, pub flags: FilterFlags,
pub flags_mask: FilterFlags, pub flags_mask: FilterFlags,

96
src/master_server.rs

@ -14,7 +14,7 @@ use thiserror::Error;
use crate::client::Packet; use crate::client::Packet;
use crate::config::{self, Config}; use crate::config::{self, Config};
use crate::filter::Filter; use crate::filter::{Filter, Version};
use crate::server::Server; use crate::server::Server;
use crate::server_info::Region; use crate::server_info::Region;
@ -74,13 +74,19 @@ impl<T> Deref for Entry<T> {
struct MasterServer { struct MasterServer {
sock: UdpSocket, sock: UdpSocket,
start_time: Instant,
challenges: HashMap<SocketAddrV4, Entry<u32>>, challenges: HashMap<SocketAddrV4, Entry<u32>>,
servers: HashMap<SocketAddrV4, Entry<Server>>, servers: HashMap<SocketAddrV4, Entry<Server>>,
rng: Rng, rng: Rng,
start_time: Instant,
cleanup_challenges: usize, cleanup_challenges: usize,
cleanup_servers: usize, cleanup_servers: usize,
timeout: config::TimeoutConfig, timeout: config::TimeoutConfig,
clver: Version,
update_title: Box<str>,
update_map: Box<str>,
update_addr: SocketAddrV4,
} }
impl MasterServer { impl MasterServer {
@ -88,6 +94,13 @@ impl MasterServer {
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port); let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
info!("Listen address: {}", addr); info!("Listen address: {}", addr);
let sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?; let sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?;
let update_addr =
cfg.client
.update_addr
.unwrap_or_else(|| match sock.local_addr().unwrap() {
SocketAddr::V4(addr) => addr,
_ => todo!(),
});
Ok(Self { Ok(Self {
sock, sock,
@ -98,6 +111,10 @@ impl MasterServer {
cleanup_challenges: 0, cleanup_challenges: 0,
cleanup_servers: 0, cleanup_servers: 0,
timeout: cfg.server.timeout, timeout: cfg.server.timeout,
clver: cfg.client.version,
update_title: cfg.client.update_title,
update_map: cfg.client.update_map,
update_addr,
}) })
} }
@ -120,7 +137,14 @@ impl MasterServer {
} }
fn handle_packet(&mut self, from: SocketAddrV4, s: &[u8]) -> Result<(), Error> { fn handle_packet(&mut self, from: SocketAddrV4, s: &[u8]) -> Result<(), Error> {
let packet = Packet::decode(s)?; let packet = match Packet::decode(s) {
Ok(p) => p,
Err(_) => {
trace!("{}: Failed to decode {:?}", from, s);
return Ok(());
}
};
trace!("{}: recv {:?}", from, packet); trace!("{}: recv {:?}", from, packet);
match packet { match packet {
@ -158,13 +182,48 @@ impl MasterServer {
self.remove_outdated_servers(); self.remove_outdated_servers();
} }
Packet::ServerRemove => { /* ignore */ } Packet::ServerRemove => { /* ignore */ }
Packet::QueryServers(region, filter) => match Filter::from_bytes(&filter) { Packet::QueryServers(region, filter) => {
Ok(filter) => self.send_server_list(from, region, &filter)?, let filter = match Filter::from_bytes(&filter) {
_ => { Ok(f) => f,
warn!("{}: Invalid filter: {:?}", from, filter); _ => {
return Ok(()); warn!("{}: Invalid filter: {:?}", from, filter);
return Ok(());
}
};
if filter.clver.map_or(true, |v| v < self.clver) {
let iter = std::iter::once(&self.update_addr);
self.send_server_list(from, iter)?;
} else {
let now = self.now();
let iter = self
.servers
.iter()
.filter(|i| i.1.is_valid(now, self.timeout.server))
.filter(|i| i.1.matches(*i.0, region, &filter))
.map(|i| i.0);
self.send_server_list(from, iter)?;
} }
}, }
Packet::ServerInfo => {
let mut buf = [0; MAX_PACKET_SIZE];
let mut cur = Cursor::new(&mut buf[..]);
cur.write_all(b"\xff\xff\xff\xffinfo\n")?;
cur.write_all(b"\\p\\49")?;
cur.write_all(b"\\map\\")?;
cur.write_all(self.update_map.as_bytes())?;
cur.write_all(b"\\dm\\1")?;
cur.write_all(b"\\team\\0")?;
cur.write_all(b"\\coop\\0")?;
cur.write_all(b"\\numcl\\0")?;
cur.write_all(b"\\maxcl\\0")?;
cur.write_all(b"\\gamedir\\valve")?;
cur.write_all(b"\\password\\0")?;
cur.write_all(b"\\host\\")?;
cur.write_all(self.update_title.as_bytes())?;
let n = cur.position() as usize;
self.sock.send_to(&buf[..n], from)?;
}
} }
Ok(()) Ok(())
@ -240,20 +299,11 @@ impl MasterServer {
Ok(()) Ok(())
} }
fn send_server_list<A: ToSocketAddrs>( fn send_server_list<'a, A, I>(&self, to: A, mut iter: I) -> Result<(), io::Error>
&self, where
to: A, A: ToSocketAddrs,
region: Region, I: Iterator<Item = &'a SocketAddrV4>,
filter: &Filter, {
) -> Result<(), io::Error> {
let now = self.now();
let mut iter = self
.servers
.iter()
.filter(|i| i.1.is_valid(now, self.timeout.server))
.filter(|i| i.1.matches(*i.0, region, filter))
.map(|i| i.0);
let mut buf = [0; MAX_PACKET_SIZE]; let mut buf = [0; MAX_PACKET_SIZE];
let mut done = false; let mut done = false;
while !done { while !done {

7
src/parser.rs

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com> // SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
use std::num::ParseIntError;
use std::str; use std::str;
use thiserror::Error; use thiserror::Error;
@ -19,6 +20,12 @@ pub enum Error {
InvalidInteger, InvalidInteger,
} }
impl From<ParseIntError> for Error {
fn from(_: ParseIntError) -> Self {
Error::InvalidInteger
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Parser<'a> { pub struct Parser<'a> {

Loading…
Cancel
Save