From 4039b9fc438f7b49f740645f3697ca8bd5173e83 Mon Sep 17 00:00:00 2001 From: Denis Drakhnia Date: Thu, 2 Nov 2023 17:07:04 +0200 Subject: [PATCH] master: reload config with SIGUSR1 --- Cargo.lock | 20 +++++++ master/Cargo.toml | 1 + master/config/main.toml | 4 +- master/src/logger.rs | 5 +- master/src/main.rs | 69 ++++++++++++++++++------ master/src/master_server.rs | 105 +++++++++++++++++++++++------------- 6 files changed, 148 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49453ec..2ce3704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,25 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "syn" version = "2.0.37" @@ -491,6 +510,7 @@ dependencies = [ "log", "once_cell", "serde", + "signal-hook", "thiserror", "toml", "xash3d-protocol", diff --git a/master/Cargo.toml b/master/Cargo.toml index 50af3b2..bd5c158 100644 --- a/master/Cargo.toml +++ b/master/Cargo.toml @@ -19,6 +19,7 @@ fastrand = "2.0.1" serde = { version = "1.0.188", features = ["derive"] } toml = "0.5.11" blake2b_simd = "<0.6" +signal-hook = { version = "0.3.17", default-features = false } xash3d-protocol = { path = "../protocol", version = "0.1.0" } [dependencies.chrono] diff --git a/master/config/main.toml b/master/config/main.toml index bb47014..899ce96 100644 --- a/master/config/main.toml +++ b/master/config/main.toml @@ -2,7 +2,7 @@ [log] # Possible values: 0-5, off, error, warn, info, debug, trace -level = "warn" +level = "info" [server] ip = "0.0.0.0" @@ -21,7 +21,7 @@ admin = 10 version = "0.19" update_title = "https://github.com/FWGS/xash3d-fwgs" update_map = "Update please" -update_addr = "mentality.rip:27010" +update_addr = "mentality.rip" [hash] len = 64 diff --git a/master/src/logger.rs b/master/src/logger.rs index 1f0d0c0..c3881e5 100644 --- a/master/src/logger.rs +++ b/master/src/logger.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only // SPDX-FileCopyrightText: 2023 Denis Drakhnia -use log::{LevelFilter, Metadata, Record}; +use log::{Metadata, Record}; struct Logger; @@ -30,9 +30,8 @@ impl log::Log for Logger { static LOGGER: Logger = Logger; -pub fn init(level_filter: LevelFilter) { +pub fn init() { if let Err(e) = log::set_logger(&LOGGER) { eprintln!("Failed to initialize logger: {}", e); } - log::set_max_level(level_filter); } diff --git a/master/src/main.rs b/master/src/main.rs index 6640638..70957ff 100644 --- a/master/src/main.rs +++ b/master/src/main.rs @@ -6,35 +6,74 @@ mod config; mod logger; mod master_server; -use log::error; +use std::process; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; -fn main() { - let cli = cli::parse().unwrap_or_else(|e| { - eprintln!("{}", e); - std::process::exit(1); - }); +use log::{error, info}; +use signal_hook::consts::signal::*; +use signal_hook::flag as signal_flag; - 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); - }); +use crate::cli::Cli; +use crate::config::Config; +use crate::master_server::{Error, MasterServer}; + +fn load_config(cli: &Cli) -> Result { + let mut cfg = config::load(cli.config_path.as_ref())?; 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); + log::set_max_level(cfg.log.level); - if let Err(e) = master_server::run(cfg) { - error!("{}", e); + Ok(cfg) +} + +fn run() -> Result<(), Error> { + let cli = cli::parse().unwrap_or_else(|e| { + eprintln!("{}", e); std::process::exit(1); + }); + + logger::init(); + + let cfg = load_config(&cli).unwrap_or_else(|e| { + eprintln!("Failed to load config \"{}\": {}", cli.config_path, e); + process::exit(1); + }); + + let mut master = MasterServer::new(cfg)?; + let sig_flag = Arc::new(AtomicBool::new(false)); + signal_flag::register(SIGUSR1, sig_flag.clone())?; + + loop { + master.run(&sig_flag)?; + + if sig_flag.swap(false, Ordering::Relaxed) { + info!("Reloading config from {}", cli.config_path); + + match load_config(&cli) { + Ok(cfg) => { + if let Err(e) = master.update_config(cfg) { + error!("{}", e); + } + } + Err(e) => error!("failed to load config: {}", e), + } + } + } +} + +fn main() { + if let Err(e) = run() { + error!("{}", e); + process::exit(1); } } diff --git a/master/src/master_server.rs b/master/src/master_server.rs index e6de3c9..72259f2 100644 --- a/master/src/master_server.rs +++ b/master/src/master_server.rs @@ -5,7 +5,8 @@ use std::collections::{HashMap, HashSet}; use std::io; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket}; use std::ops::Deref; -use std::time::Instant; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::{Duration, Instant}; use blake2b_simd::Params; use fastrand::Rng; @@ -97,7 +98,7 @@ impl Counter { } } -struct MasterServer { +pub struct MasterServer { sock: UdpSocket, challenges: HashMap>, challenges_counter: Counter, @@ -137,38 +138,36 @@ where Ok(None) } +fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddrV4 { + if let Some(s) = cfg.client.update_addr.as_deref() { + let addr = if !s.contains(':') { + format!("{}:{}", s, local_addr.port()) + } else { + s.to_owned() + }; + + match resolve_socket_addr(&addr) { + Ok(Some(x)) => return x, + Ok(None) => error!("Update address: failed to resolve IPv4 for \"{}\"", addr), + Err(e) => error!("Update address: {}", e), + } + } + + match local_addr { + SocketAddr::V4(x) => x, + SocketAddr::V6(_) => todo!(), + } +} + impl MasterServer { - fn new(cfg: Config) -> Result { + pub fn new(cfg: Config) -> Result { let addr = SocketAddr::new(cfg.server.ip, cfg.server.port); info!("Listen address: {}", addr); let sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?; + // make socket interruptable by singals + sock.set_read_timeout(Some(Duration::from_secs(u32::MAX as u64)))?; - let update_addr = { - let mut addr = None; - - if let Some(update_addr) = cfg.client.update_addr { - addr = match resolve_socket_addr(&*update_addr) { - Ok(None) => { - error!( - "update address: failed to resolve IPv4 for \"{}\"", - update_addr - ); - None - } - Err(e) => { - error!("update address: {}", e); - None - } - Ok(addr) => addr, - } - }; - - // fallback to local address - addr.unwrap_or_else(|| match sock.local_addr().unwrap() { - SocketAddr::V4(a) => a, - _ => todo!(), - }) - }; + let update_addr = resolve_update_addr(&cfg, addr); Ok(Self { sock, @@ -193,10 +192,40 @@ impl MasterServer { }) } - fn run(&mut self) -> Result<(), Error> { + pub fn update_config(&mut self, cfg: Config) -> Result<(), Error> { + let local_addr = self.sock.local_addr()?; + let addr = SocketAddr::new(cfg.server.ip, cfg.server.port); + if local_addr != addr { + info!("Listen address: {}", addr); + self.sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?; + // make socket interruptable by singals + self.sock + .set_read_timeout(Some(Duration::from_secs(u32::MAX as u64)))?; + self.clear(); + } + + self.update_addr = resolve_update_addr(&cfg, addr); + self.timeout = cfg.server.timeout; + self.clver = cfg.client.version; + self.update_title = cfg.client.update_title; + self.update_map = cfg.client.update_map; + self.admin_list = cfg.admin_list; + self.hash = cfg.hash; + + Ok(()) + } + + pub fn run(&mut self, sig_flag: &AtomicBool) -> Result<(), Error> { let mut buf = [0; MAX_PACKET_SIZE]; - loop { - let (n, from) = self.sock.recv_from(&mut buf)?; + while !sig_flag.load(Ordering::Relaxed) { + let (n, from) = match self.sock.recv_from(&mut buf) { + Ok(x) => x, + Err(e) => match e.kind() { + io::ErrorKind::Interrupted => break, + io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => continue, + _ => Err(e)?, + }, + }; let from = match from { SocketAddr::V4(a) => a, @@ -210,6 +239,14 @@ impl MasterServer { error!("{}: {}", from, e); } } + Ok(()) + } + + fn clear(&mut self) { + info!("Clear all servers and challenges"); + self.challenges.clear(); + self.servers.clear(); + self.admin_challenges.clear(); } fn handle_packet(&mut self, from: SocketAddrV4, src: &[u8]) -> Result<(), Error> { @@ -505,7 +542,3 @@ impl MasterServer { } } } - -pub fn run(cfg: Config) -> Result<(), Error> { - MasterServer::new(cfg)?.run() -}