mirror of
https://git.mentality.rip/numas13/xash3d-master.git
synced 2025-02-02 10:14:24 +00:00
refactor, use one binary for both ip versions
This commit is contained in:
parent
f7de9f6f0d
commit
ce759fed3d
@ -1,22 +1,18 @@
|
|||||||
// 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::fs;
|
use std::{
|
||||||
use std::io;
|
fs, io,
|
||||||
use std::net::IpAddr;
|
net::{IpAddr, Ipv4Addr},
|
||||||
use std::path::Path;
|
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 xash3d_protocol::admin;
|
use xash3d_protocol::{admin, filter::Version};
|
||||||
use xash3d_protocol::filter::Version;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ipv6"))]
|
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
|
||||||
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED);
|
|
||||||
|
|
||||||
#[cfg(feature = "ipv6")]
|
|
||||||
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED);
|
|
||||||
|
|
||||||
pub const DEFAULT_MASTER_SERVER_PORT: u16 = 27010;
|
pub const DEFAULT_MASTER_SERVER_PORT: u16 = 27010;
|
||||||
pub const DEFAULT_CHALLENGE_TIMEOUT: u32 = 10;
|
pub const DEFAULT_CHALLENGE_TIMEOUT: u32 = 10;
|
||||||
|
@ -23,7 +23,7 @@ use signal_hook::{consts::signal::*, flag as signal_flag};
|
|||||||
|
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::master_server::{Error, MasterServer};
|
use crate::master_server::{Error, Master};
|
||||||
|
|
||||||
fn load_config(cli: &Cli) -> Result<Config, config::Error> {
|
fn load_config(cli: &Cli) -> Result<Config, config::Error> {
|
||||||
let mut cfg = match cli.config_path {
|
let mut cfg = match cli.config_path {
|
||||||
@ -68,7 +68,7 @@ fn run() -> Result<(), Error> {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut master = MasterServer::new(cfg)?;
|
let mut master = Master::new(cfg)?;
|
||||||
let sig_flag = Arc::new(AtomicBool::new(false));
|
let sig_flag = Arc::new(AtomicBool::new(false));
|
||||||
// XXX: Windows does not support SIGUSR1.
|
// XXX: Windows does not support SIGUSR1.
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp::Eq,
|
||||||
collections::hash_map,
|
collections::hash_map,
|
||||||
|
fmt::Display,
|
||||||
|
hash::Hash,
|
||||||
io,
|
io,
|
||||||
net::{SocketAddr, ToSocketAddrs, UdpSocket},
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, UdpSocket},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
@ -31,60 +34,54 @@ use crate::{
|
|||||||
stats::Stats,
|
stats::Stats,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "ipv6"))]
|
pub trait AddrExt: Sized + Eq + Hash + Display + Copy + ToSocketAddrs + ServerAddress {
|
||||||
mod protocol {
|
type Ip;
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
pub type Addr = std::net::SocketAddrV4;
|
fn extract(addr: SocketAddr) -> Result<Self, SocketAddr>;
|
||||||
pub type Ip = std::net::Ipv4Addr;
|
fn ip(&self) -> &Self::Ip;
|
||||||
|
fn wrap(self) -> SocketAddr;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extract_addr(addr: SocketAddr) -> Option<Addr> {
|
impl AddrExt for SocketAddrV4 {
|
||||||
if let SocketAddr::V4(a) = addr {
|
type Ip = Ipv4Addr;
|
||||||
Some(a)
|
|
||||||
|
fn extract(addr: SocketAddr) -> Result<Self, SocketAddr> {
|
||||||
|
if let SocketAddr::V4(addr) = addr {
|
||||||
|
Ok(addr)
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn ip(&self) -> &Self::Ip {
|
||||||
pub fn wrap_addr(addr: Addr) -> SocketAddr {
|
SocketAddrV4::ip(self)
|
||||||
SocketAddr::V4(addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn wrap(self) -> SocketAddr {
|
||||||
pub fn check_addr(addr: &SocketAddr) -> bool {
|
SocketAddr::V4(self)
|
||||||
addr.is_ipv4()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ipv6")]
|
impl AddrExt for SocketAddrV6 {
|
||||||
mod protocol {
|
type Ip = Ipv6Addr;
|
||||||
use std::net::SocketAddr;
|
|
||||||
|
|
||||||
pub type Addr = std::net::SocketAddrV6;
|
fn extract(addr: SocketAddr) -> Result<Self, SocketAddr> {
|
||||||
pub type Ip = std::net::Ipv6Addr;
|
if let SocketAddr::V6(addr) = addr {
|
||||||
|
Ok(addr)
|
||||||
pub fn extract_addr(addr: SocketAddr) -> Option<Addr> {
|
|
||||||
if let SocketAddr::V6(a) = addr {
|
|
||||||
Some(a)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn ip(&self) -> &Self::Ip {
|
||||||
pub fn wrap_addr(addr: Addr) -> SocketAddr {
|
SocketAddrV6::ip(self)
|
||||||
SocketAddr::V6(addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn wrap(self) -> SocketAddr {
|
||||||
pub fn check_addr(addr: &SocketAddr) -> bool {
|
SocketAddr::V6(self)
|
||||||
addr.is_ipv6()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use self::protocol::*;
|
|
||||||
|
|
||||||
/// The maximum size of UDP packets.
|
/// The maximum size of UDP packets.
|
||||||
const MAX_PACKET_SIZE: usize = 512;
|
const MAX_PACKET_SIZE: usize = 512;
|
||||||
|
|
||||||
@ -104,8 +101,6 @@ const ADMIN_LIMIT_CLEANUP_MAX: usize = 100;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to bind server socket: {0}")]
|
#[error("Failed to bind server socket: {0}")]
|
||||||
BindSocket(io::Error),
|
BindSocket(io::Error),
|
||||||
#[error("IP version is not supported")]
|
|
||||||
Unsupported,
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Protocol(#[from] ProtocolError),
|
Protocol(#[from] ProtocolError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@ -136,8 +131,8 @@ impl<T> Entry<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Entry<ServerInfo> {
|
impl Entry<ServerInfo> {
|
||||||
fn matches(&self, addr: Addr, region: Region, filter: &Filter) -> bool {
|
fn matches<Addr: AddrExt>(&self, addr: Addr, region: Region, filter: &Filter) -> bool {
|
||||||
self.region == region && filter.matches(wrap_addr(addr), &self.value)
|
self.region == region && filter.matches(addr.wrap(), &self.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +165,7 @@ impl Counter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MasterServer {
|
pub struct MasterServer<Addr: AddrExt> {
|
||||||
sock: UdpSocket,
|
sock: UdpSocket,
|
||||||
challenges: HashMap<Addr, Entry<u32>>,
|
challenges: HashMap<Addr, Entry<u32>>,
|
||||||
challenges_counter: Counter,
|
challenges_counter: Counter,
|
||||||
@ -187,15 +182,15 @@ pub struct MasterServer {
|
|||||||
update_map: Box<str>,
|
update_map: Box<str>,
|
||||||
update_addr: SocketAddr,
|
update_addr: SocketAddr,
|
||||||
|
|
||||||
admin_challenges: HashMap<Ip, Entry<(u32, u32)>>,
|
admin_challenges: HashMap<Addr::Ip, Entry<(u32, u32)>>,
|
||||||
admin_challenges_counter: Counter,
|
admin_challenges_counter: Counter,
|
||||||
admin_list: Box<[config::AdminConfig]>,
|
admin_list: Box<[config::AdminConfig]>,
|
||||||
// rate limit if hash is invalid
|
// rate limit if hash is invalid
|
||||||
admin_limit: HashMap<Ip, Entry<()>>,
|
admin_limit: HashMap<Addr::Ip, Entry<()>>,
|
||||||
admin_limit_counter: Counter,
|
admin_limit_counter: Counter,
|
||||||
hash: config::HashConfig,
|
hash: config::HashConfig,
|
||||||
|
|
||||||
blocklist: HashSet<Ip>,
|
blocklist: HashSet<Addr::Ip>,
|
||||||
|
|
||||||
stats: Stats,
|
stats: Stats,
|
||||||
|
|
||||||
@ -233,18 +228,52 @@ fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddr {
|
|||||||
local_addr
|
local_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterServer {
|
pub enum Master {
|
||||||
|
V4(MasterServer<SocketAddrV4>),
|
||||||
|
V6(MasterServer<SocketAddrV6>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Master {
|
||||||
pub fn new(cfg: Config) -> Result<Self, Error> {
|
pub fn new(cfg: Config) -> Result<Self, Error> {
|
||||||
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
|
match SocketAddr::new(cfg.server.ip, cfg.server.port) {
|
||||||
if !check_addr(&addr) {
|
SocketAddr::V4(addr) => MasterServer::new(cfg, addr).map(Self::V4),
|
||||||
return Err(Error::Unsupported);
|
SocketAddr::V6(addr) => MasterServer::new(cfg, addr).map(Self::V6),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_config(&mut self, cfg: Config) -> Result<(), Error> {
|
||||||
|
let cfg = match self {
|
||||||
|
Self::V4(inner) => inner.update_config(cfg)?,
|
||||||
|
Self::V6(inner) => inner.update_config(cfg)?,
|
||||||
|
};
|
||||||
|
if let Some(cfg) = cfg {
|
||||||
|
info!("Server IP version changed, full restart");
|
||||||
|
*self = Self::new(cfg)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, sig_flag: &AtomicBool) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::V4(inner) => inner.run(sig_flag),
|
||||||
|
Self::V6(inner) => inner.run(sig_flag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Addr> MasterServer<Addr>
|
||||||
|
where
|
||||||
|
Addr: AddrExt,
|
||||||
|
Addr::Ip: Eq + Hash + Display + Copy + std::str::FromStr,
|
||||||
|
{
|
||||||
|
pub fn new(cfg: Config, addr: Addr) -> Result<Self, Error> {
|
||||||
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)?;
|
||||||
// make socket interruptable by singals
|
// make socket interruptable by singals
|
||||||
sock.set_read_timeout(Some(Duration::from_secs(u32::MAX as u64)))?;
|
sock.set_read_timeout(Some(Duration::from_secs(u32::MAX as u64)))?;
|
||||||
|
|
||||||
let update_addr = resolve_update_addr(&cfg, addr);
|
let update_addr = resolve_update_addr(&cfg, addr.wrap());
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
sock,
|
sock,
|
||||||
@ -274,10 +303,16 @@ impl MasterServer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_config(&mut self, cfg: Config) -> Result<(), Error> {
|
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||||
let local_addr = self.sock.local_addr()?;
|
self.sock.local_addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_config(&mut self, cfg: Config) -> Result<Option<Config>, Error> {
|
||||||
|
let local_addr = self.local_addr()?;
|
||||||
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
|
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
|
||||||
if local_addr != addr {
|
if local_addr.is_ipv4() != addr.is_ipv4() {
|
||||||
|
return Ok(Some(cfg));
|
||||||
|
} else if local_addr != addr {
|
||||||
info!("Listen address: {}", addr);
|
info!("Listen address: {}", addr);
|
||||||
self.sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?;
|
self.sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?;
|
||||||
// make socket interruptable by singals
|
// make socket interruptable by singals
|
||||||
@ -295,7 +330,7 @@ impl MasterServer {
|
|||||||
self.hash = cfg.hash;
|
self.hash = cfg.hash;
|
||||||
self.stats.update_config(cfg.stat);
|
self.stats.update_config(cfg.stat);
|
||||||
|
|
||||||
Ok(())
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, sig_flag: &AtomicBool) -> Result<(), Error> {
|
pub fn run(&mut self, sig_flag: &AtomicBool) -> Result<(), Error> {
|
||||||
@ -310,14 +345,9 @@ impl MasterServer {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let from = match extract_addr(from) {
|
let from = match Addr::extract(from) {
|
||||||
Some(from) => from,
|
Ok(from) => from,
|
||||||
None => {
|
Err(_) => continue,
|
||||||
if from.is_ipv6() {
|
|
||||||
debug!("{}: IPv6 is not implemented", from);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let src = &buf[..n];
|
let src = &buf[..n];
|
||||||
@ -596,7 +626,7 @@ impl MasterServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_servers(&self, ip: &Ip) -> u16 {
|
fn count_servers(&self, ip: &Addr::Ip) -> u16 {
|
||||||
self.servers.keys().filter(|i| i.ip() == ip).count() as u16
|
self.servers.keys().filter(|i| i.ip() == ip).count() as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +675,7 @@ impl MasterServer {
|
|||||||
|
|
||||||
fn send_client_to_nat_servers(&self, to: Addr, servers: &[Addr]) -> Result<(), Error> {
|
fn send_client_to_nat_servers(&self, to: Addr, servers: &[Addr]) -> Result<(), Error> {
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
let n = master::ClientAnnounce::new(wrap_addr(to)).encode(&mut buf)?;
|
let n = master::ClientAnnounce::new(to.wrap()).encode(&mut buf)?;
|
||||||
let buf = &buf[..n];
|
let buf = &buf[..n];
|
||||||
for i in servers {
|
for i in servers {
|
||||||
self.sock.send_to(buf, i)?;
|
self.sock.send_to(buf, i)?;
|
||||||
@ -654,15 +684,20 @@ impl MasterServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_blocked(&self, ip: &Ip) -> bool {
|
fn is_blocked(&self, ip: &Addr::Ip) -> bool {
|
||||||
self.blocklist.contains(ip)
|
self.blocklist.contains(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn admin_command(&mut self, cmd: &str) {
|
fn admin_command(&mut self, cmd: &str) {
|
||||||
let args: Vec<_> = cmd.split(' ').collect();
|
let args: Vec<_> = cmd.split(' ').collect();
|
||||||
|
|
||||||
fn helper<F: FnMut(&str, Ip)>(args: &[&str], mut op: F) {
|
fn helper<Addr, F>(args: &[&str], mut op: F)
|
||||||
let iter = args.iter().map(|i| (i, i.parse::<Ip>()));
|
where
|
||||||
|
Addr: AddrExt,
|
||||||
|
Addr::Ip: std::str::FromStr,
|
||||||
|
F: FnMut(&str, Addr::Ip),
|
||||||
|
{
|
||||||
|
let iter = args.iter().map(|i| (i, i.parse::<Addr::Ip>()));
|
||||||
for (i, ip) in iter {
|
for (i, ip) in iter {
|
||||||
match ip {
|
match ip {
|
||||||
Ok(ip) => op(i, ip),
|
Ok(ip) => op(i, ip),
|
||||||
@ -673,14 +708,14 @@ impl MasterServer {
|
|||||||
|
|
||||||
match args[0] {
|
match args[0] {
|
||||||
"ban" => {
|
"ban" => {
|
||||||
helper(&args[1..], |_, ip| {
|
helper::<Addr, _>(&args[1..], |_, ip| {
|
||||||
if self.blocklist.insert(ip) {
|
if self.blocklist.insert(ip) {
|
||||||
info!("ban ip: {}", ip);
|
info!("ban ip: {}", ip);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
"unban" => {
|
"unban" => {
|
||||||
helper(&args[1..], |_, ip| {
|
helper::<Addr, _>(&args[1..], |_, ip| {
|
||||||
if self.blocklist.remove(&ip) {
|
if self.blocklist.remove(&ip) {
|
||||||
info!("unban ip: {}", ip);
|
info!("unban ip: {}", ip);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user