Browse Source

Add config file

ipv6
Denis Drakhnia 1 year ago
parent
commit
a4e10b9965
  1. 31
      Cargo.lock
  2. 2
      Cargo.toml
  3. 15
      config/main.toml
  4. 73
      src/cli.rs
  5. 142
      src/config.rs
  6. 22
      src/main.rs
  7. 26
      src/master_server.rs

31
Cargo.lock generated

@ -93,7 +93,9 @@ dependencies = [
"getopts", "getopts",
"log", "log",
"once_cell", "once_cell",
"serde",
"thiserror", "thiserror",
"toml",
] ]
[[package]] [[package]]
@ -173,6 +175,26 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.37" version = "2.0.37"
@ -204,6 +226,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"

2
Cargo.toml

@ -16,6 +16,8 @@ getopts = "0.2.21"
log = "<0.4.19" log = "<0.4.19"
bitflags = "2.4" bitflags = "2.4"
fastrand = "2.0.1" fastrand = "2.0.1"
serde = { version = "1.0.188", features = ["derive"] }
toml = "0.5.11"
[dependencies.chrono] [dependencies.chrono]
version = "<0.4.27" version = "<0.4.27"

15
config/main.toml

@ -0,0 +1,15 @@
# Default config file
[log]
# Possible values: 0-5, off, error, warn, info, debug, trace
level = "warn"
[server]
ip = "0.0.0.0"
port = 27010
[server.timeout]
# Time in seconds while challenge is valid
challenge = 300
# Time in seconds while server is valid
server = 300

73
src/cli.rs

@ -1,19 +1,19 @@
// 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::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::IpAddr;
use std::process; use std::process;
use getopts::Options; use getopts::Options;
use log::LevelFilter; use log::LevelFilter;
use thiserror::Error; use thiserror::Error;
use crate::config;
const BIN_NAME: &str = env!("CARGO_BIN_NAME"); const BIN_NAME: &str = env!("CARGO_BIN_NAME");
const PKG_NAME: &str = env!("CARGO_PKG_NAME"); const PKG_NAME: &str = env!("CARGO_PKG_NAME");
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
const DEFAULT_MASTER_SERVER_PORT: u16 = 27010;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("Invalid ip address \"{0}\"")] #[error("Invalid ip address \"{0}\"")]
@ -24,22 +24,12 @@ pub enum Error {
Options(#[from] getopts::Fail), Options(#[from] getopts::Fail),
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Cli { pub struct Cli {
pub log_level: LevelFilter, pub log_level: Option<LevelFilter>,
pub listen: SocketAddr, pub listen_ip: Option<IpAddr>,
} pub listen_port: Option<u16>,
pub config_path: Box<str>,
impl Default for Cli {
fn default() -> Self {
Self {
log_level: LevelFilter::Warn,
listen: SocketAddr::new(
IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)),
DEFAULT_MASTER_SERVER_PORT,
),
}
}
} }
fn print_usage(opts: Options) { fn print_usage(opts: Options) {
@ -52,7 +42,10 @@ fn print_version() {
} }
pub fn parse() -> Result<Cli, Error> { pub fn parse() -> Result<Cli, Error> {
let mut cli = Cli::default(); let mut cli = Cli {
config_path: config::DEFAULT_CONFIG_PATH.to_string().into_boxed_str(),
..Cli::default()
};
let args: Vec<_> = std::env::args().collect(); let args: Vec<_> = std::env::args().collect();
let mut opts = Options::new(); let mut opts = Options::new();
@ -61,10 +54,15 @@ pub fn parse() -> Result<Cli, Error> {
let log_help = let log_help =
"logging level [default: warn(2)]\nLEVEL: 0-5, off, error, warn, info, debug, trace"; "logging level [default: warn(2)]\nLEVEL: 0-5, off, error, warn, info, debug, trace";
opts.optopt("l", "log", log_help, "LEVEL"); opts.optopt("l", "log", log_help, "LEVEL");
let ip_help = format!("listen ip [default: {}]", cli.listen.ip()); let ip_help = format!("listen ip [default: {}]", config::DEFAULT_MASTER_SERVER_IP);
opts.optopt("i", "ip", &ip_help, "IP"); opts.optopt("i", "ip", &ip_help, "IP");
let port_help = format!("listen port [default: {}]", cli.listen.port()); let port_help = format!(
"listen port [default: {}]",
config::DEFAULT_MASTER_SERVER_PORT
);
opts.optopt("p", "port", &port_help, "PORT"); opts.optopt("p", "port", &port_help, "PORT");
let config_help = format!("config path [default: {}]", cli.config_path);
opts.optopt("c", "config", &config_help, "PATH");
let matches = opts.parse(&args[1..])?; let matches = opts.parse(&args[1..])?;
@ -79,38 +77,25 @@ pub fn parse() -> Result<Cli, Error> {
} }
if let Some(value) = matches.opt_str("log") { if let Some(value) = matches.opt_str("log") {
use LevelFilter as E; match config::parse_log_level(value.as_ref()) {
Some(level) => cli.log_level = Some(level),
cli.log_level = match value.as_str() { None => {
_ if "off".starts_with(&value) => E::Off,
_ if "error".starts_with(&value) => E::Error,
_ if "warn".starts_with(&value) => E::Warn,
_ if "info".starts_with(&value) => E::Info,
_ if "debug".starts_with(&value) => E::Debug,
_ if "trace".starts_with(&value) => E::Trace,
_ => match value.parse::<u8>() {
Ok(0) => E::Off,
Ok(1) => E::Error,
Ok(2) => E::Warn,
Ok(3) => E::Info,
Ok(4) => E::Debug,
Ok(5) => E::Trace,
_ => {
eprintln!("Invalid value for log option: \"{}\"", value); eprintln!("Invalid value for log option: \"{}\"", value);
process::exit(1); process::exit(1);
} }
}, }
};
} }
if let Some(s) = matches.opt_str("ip") { if let Some(s) = matches.opt_str("ip") {
cli.listen cli.listen_ip = Some(s.parse().map_err(|_| Error::InvalidIp(s))?);
.set_ip(s.parse().map_err(|_| Error::InvalidIp(s))?);
} }
if let Some(s) = matches.opt_str("port") { if let Some(s) = matches.opt_str("port") {
cli.listen cli.listen_port = Some(s.parse().map_err(|_| Error::InvalidPort(s))?);
.set_port(s.parse().map_err(|_| Error::InvalidPort(s))?); }
if let Some(s) = matches.opt_str("config") {
cli.config_path = s.into_boxed_str();
} }
Ok(cli) Ok(cli)

142
src/config.rs

@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
use std::fs;
use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
use log::LevelFilter;
use serde::{de::Error as _, Deserialize, Deserializer};
use thiserror::Error;
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_PORT: u16 = 27010;
pub const DEFAULT_TIMEOUT: u32 = 300;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Toml(#[from] toml::de::Error),
#[error(transparent)]
Io(#[from] io::Error),
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub log: LogConfig,
#[serde(default)]
pub server: ServerConfig,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct LogConfig {
#[serde(default = "default_log_level")]
#[serde(deserialize_with = "deserialize_log_level")]
pub level: LevelFilter,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: default_log_level(),
}
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ServerConfig {
#[serde(default = "default_server_ip")]
pub ip: IpAddr,
#[serde(default = "default_server_port")]
pub port: u16,
#[serde(default)]
pub timeout: TimeoutConfig,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
ip: default_server_ip(),
port: default_server_port(),
timeout: Default::default(),
}
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct TimeoutConfig {
#[serde(default = "default_timeout")]
pub challenge: u32,
#[serde(default = "default_timeout")]
pub server: u32,
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
challenge: default_timeout(),
server: default_timeout(),
}
}
}
fn default_log_level() -> LevelFilter {
LevelFilter::Warn
}
fn default_server_ip() -> IpAddr {
DEFAULT_MASTER_SERVER_IP
}
fn default_server_port() -> u16 {
DEFAULT_MASTER_SERVER_PORT
}
fn default_timeout() -> u32 {
DEFAULT_TIMEOUT
}
fn deserialize_log_level<'de, D>(de: D) -> Result<LevelFilter, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(de)?;
parse_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> {
use LevelFilter as E;
let level_filter = match s {
_ if "off".starts_with(s) => E::Off,
_ if "error".starts_with(s) => E::Error,
_ if "warn".starts_with(s) => E::Warn,
_ if "info".starts_with(s) => E::Info,
_ if "debug".starts_with(s) => E::Debug,
_ if "trace".starts_with(s) => E::Trace,
_ => match s.parse::<u8>() {
Ok(0) => E::Off,
Ok(1) => E::Error,
Ok(2) => E::Warn,
Ok(3) => E::Info,
Ok(4) => E::Debug,
Ok(5) => E::Trace,
_ => return None,
},
};
Some(level_filter)
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
let data = fs::read(path)?;
Ok(toml::de::from_slice(&data)?)
}

22
src/main.rs

@ -3,6 +3,7 @@
mod cli; mod cli;
mod client; mod client;
mod config;
mod filter; mod filter;
mod logger; mod logger;
mod master_server; mod master_server;
@ -18,9 +19,26 @@ fn main() {
std::process::exit(1); std::process::exit(1);
}); });
logger::init(cli.log_level); let mut cfg = config::load(cli.config_path.as_ref()).unwrap_or_else(|e| {
eprintln!("Failed to load config \"{}\": {}", cli.config_path, e);
std::process::exit(1);
});
if let Some(level) = cli.log_level {
cfg.log.level = level;
}
if let Some(ip) = cli.listen_ip {
cfg.server.ip = ip;
}
if let Some(port) = cli.listen_port {
cfg.server.port = port;
}
logger::init(cfg.log.level);
if let Err(e) = master_server::run(cli.listen) { if let Err(e) = master_server::run(cfg) {
error!("{}", e); error!("{}", e);
std::process::exit(1); std::process::exit(1);
} }

26
src/master_server.rs

@ -13,6 +13,7 @@ use log::{error, info, trace, warn};
use thiserror::Error; use thiserror::Error;
use crate::client::Packet; use crate::client::Packet;
use crate::config::{self, Config};
use crate::filter::Filter; use crate::filter::Filter;
use crate::server::Server; use crate::server::Server;
use crate::server_info::Region; use crate::server_info::Region;
@ -23,13 +24,9 @@ const MAX_PACKET_SIZE: usize = 512;
const CHALLENGE_RESPONSE_HEADER: &[u8] = b"\xff\xff\xff\xffs\n"; const CHALLENGE_RESPONSE_HEADER: &[u8] = b"\xff\xff\xff\xffs\n";
const SERVER_LIST_HEADER: &[u8] = b"\xff\xff\xff\xfff\n"; const SERVER_LIST_HEADER: &[u8] = b"\xff\xff\xff\xfff\n";
/// Time in seconds while server is valid.
const SERVER_TIMEOUT: u32 = 300;
/// How many cleanup calls should be skipped before removing outdated servers. /// How many cleanup calls should be skipped before removing outdated servers.
const SERVER_CLEANUP_MAX: usize = 100; const SERVER_CLEANUP_MAX: usize = 100;
/// Time in seconds while challenge is valid.
const CHALLENGE_TIMEOUT: u32 = 300;
/// How many cleanup calls should be skipped before removing outdated challenges. /// How many cleanup calls should be skipped before removing outdated challenges.
const CHALLENGE_CLEANUP_MAX: usize = 100; const CHALLENGE_CLEANUP_MAX: usize = 100;
@ -83,10 +80,12 @@ struct MasterServer {
rng: Rng, rng: Rng,
cleanup_challenges: usize, cleanup_challenges: usize,
cleanup_servers: usize, cleanup_servers: usize,
timeout: config::TimeoutConfig,
} }
impl MasterServer { impl MasterServer {
fn new(addr: SocketAddr) -> Result<Self, Error> { fn new(cfg: Config) -> Result<Self, Error> {
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)?;
@ -98,6 +97,7 @@ impl MasterServer {
rng: Rng::new(), rng: Rng::new(),
cleanup_challenges: 0, cleanup_challenges: 0,
cleanup_servers: 0, cleanup_servers: 0,
timeout: cfg.server.timeout,
}) })
} }
@ -142,7 +142,7 @@ impl MasterServer {
return Ok(()); return Ok(());
} }
}; };
if !entry.is_valid(self.now(), CHALLENGE_TIMEOUT) { if !entry.is_valid(self.now(), self.timeout.challenge) {
return Ok(()); return Ok(());
} }
if challenge != entry.value { if challenge != entry.value {
@ -189,7 +189,7 @@ impl MasterServer {
let now = self.now(); let now = self.now();
let old = self.challenges.len(); let old = self.challenges.len();
self.challenges self.challenges
.retain(|_, v| v.is_valid(now, CHALLENGE_TIMEOUT)); .retain(|_, v| v.is_valid(now, self.timeout.challenge));
let new = self.challenges.len(); let new = self.challenges.len();
if old != new { if old != new {
trace!("Removed {} outdated challenges", old - new); trace!("Removed {} outdated challenges", old - new);
@ -198,8 +198,7 @@ impl MasterServer {
} }
fn add_server(&mut self, addr: SocketAddrV4, server: Server) { fn add_server(&mut self, addr: SocketAddrV4, server: Server) {
let entry = Entry::new(self.now(), server); match self.servers.insert(addr, Entry::new(self.now(), server)) {
match self.servers.insert(addr, entry) {
Some(_) => trace!("{}: Updated GameServer", addr), Some(_) => trace!("{}: Updated GameServer", addr),
None => trace!("{}: New GameServer", addr), None => trace!("{}: New GameServer", addr),
} }
@ -212,7 +211,8 @@ impl MasterServer {
} }
let now = self.now(); let now = self.now();
let old = self.servers.len(); let old = self.servers.len();
self.servers.retain(|_, v| v.is_valid(now, SERVER_TIMEOUT)); self.servers
.retain(|_, v| v.is_valid(now, self.timeout.server));
let new = self.servers.len(); let new = self.servers.len();
if old != new { if old != new {
trace!("Removed {} outdated servers", old - new); trace!("Removed {} outdated servers", old - new);
@ -250,7 +250,7 @@ impl MasterServer {
let mut iter = self let mut iter = self
.servers .servers
.iter() .iter()
.filter(|i| i.1.is_valid(now, SERVER_TIMEOUT)) .filter(|i| i.1.is_valid(now, self.timeout.server))
.filter(|i| i.1.matches(*i.0, region, filter)) .filter(|i| i.1.matches(*i.0, region, filter))
.map(|i| i.0); .map(|i| i.0);
@ -287,6 +287,6 @@ impl MasterServer {
} }
} }
pub fn run(addr: SocketAddr) -> Result<(), Error> { pub fn run(cfg: Config) -> Result<(), Error> {
MasterServer::new(addr)?.run() MasterServer::new(cfg)?.run()
} }

Loading…
Cancel
Save