|
|
|
@ -1,25 +1,89 @@
@@ -1,25 +1,89 @@
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: 2023 Denis Drakhnia <numas13@gmail.com>
|
|
|
|
|
|
|
|
|
|
use std::collections::hash_map; |
|
|
|
|
use std::io; |
|
|
|
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket}; |
|
|
|
|
use std::ops::Deref; |
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering}; |
|
|
|
|
use std::time::{Duration, Instant}; |
|
|
|
|
use std::{ |
|
|
|
|
collections::hash_map, |
|
|
|
|
io, |
|
|
|
|
net::{SocketAddr, ToSocketAddrs, UdpSocket}, |
|
|
|
|
ops::Deref, |
|
|
|
|
sync::atomic::{AtomicBool, Ordering}, |
|
|
|
|
time::{Duration, Instant}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
use ahash::{AHashMap as HashMap, AHashSet as HashSet}; |
|
|
|
|
use blake2b_simd::Params; |
|
|
|
|
use fastrand::Rng; |
|
|
|
|
use log::{debug, error, info, trace, warn}; |
|
|
|
|
use thiserror::Error; |
|
|
|
|
use xash3d_protocol::filter::{Filter, FilterFlags, Version}; |
|
|
|
|
use xash3d_protocol::server::Region; |
|
|
|
|
use xash3d_protocol::wrappers::Str; |
|
|
|
|
use xash3d_protocol::{admin, game, master, server, Error as ProtocolError, ServerInfo}; |
|
|
|
|
use xash3d_protocol::{ |
|
|
|
|
admin, |
|
|
|
|
filter::{Filter, FilterFlags, Version}, |
|
|
|
|
game, |
|
|
|
|
master::{self, ServerAddress}, |
|
|
|
|
server, |
|
|
|
|
server::Region, |
|
|
|
|
wrappers::Str, |
|
|
|
|
Error as ProtocolError, ServerInfo, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
use crate::{ |
|
|
|
|
config::{self, Config}, |
|
|
|
|
stats::Stats, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#[cfg(not(feature = "ipv6"))] |
|
|
|
|
mod protocol { |
|
|
|
|
use std::net::SocketAddr; |
|
|
|
|
|
|
|
|
|
pub type Addr = std::net::SocketAddrV4; |
|
|
|
|
pub type Ip = std::net::Ipv4Addr; |
|
|
|
|
|
|
|
|
|
pub fn extract_addr(addr: SocketAddr) -> Option<Addr> { |
|
|
|
|
if let SocketAddr::V4(a) = addr { |
|
|
|
|
Some(a) |
|
|
|
|
} else { |
|
|
|
|
None |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[inline(always)] |
|
|
|
|
pub fn wrap_addr(addr: Addr) -> SocketAddr { |
|
|
|
|
SocketAddr::V4(addr) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[inline(always)] |
|
|
|
|
pub fn check_addr(addr: &SocketAddr) -> bool { |
|
|
|
|
addr.is_ipv4() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
use crate::config::{self, Config}; |
|
|
|
|
use crate::stats::Stats; |
|
|
|
|
#[cfg(feature = "ipv6")] |
|
|
|
|
mod protocol { |
|
|
|
|
use std::net::SocketAddr; |
|
|
|
|
|
|
|
|
|
pub type Addr = std::net::SocketAddrV6; |
|
|
|
|
pub type Ip = std::net::Ipv6Addr; |
|
|
|
|
|
|
|
|
|
pub fn extract_addr(addr: SocketAddr) -> Option<Addr> { |
|
|
|
|
if let SocketAddr::V6(a) = addr { |
|
|
|
|
Some(a) |
|
|
|
|
} else { |
|
|
|
|
None |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[inline(always)] |
|
|
|
|
pub fn wrap_addr(addr: Addr) -> SocketAddr { |
|
|
|
|
SocketAddr::V6(addr) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[inline(always)] |
|
|
|
|
pub fn check_addr(addr: &SocketAddr) -> bool { |
|
|
|
|
addr.is_ipv6() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
use self::protocol::*; |
|
|
|
|
|
|
|
|
|
/// The maximum size of UDP packets.
|
|
|
|
|
const MAX_PACKET_SIZE: usize = 512; |
|
|
|
@ -40,6 +104,8 @@ const ADMIN_LIMIT_CLEANUP_MAX: usize = 100;
@@ -40,6 +104,8 @@ const ADMIN_LIMIT_CLEANUP_MAX: usize = 100;
|
|
|
|
|
pub enum Error { |
|
|
|
|
#[error("Failed to bind server socket: {0}")] |
|
|
|
|
BindSocket(io::Error), |
|
|
|
|
#[error("IP version is not supported")] |
|
|
|
|
Unsupported, |
|
|
|
|
#[error(transparent)] |
|
|
|
|
Protocol(#[from] ProtocolError), |
|
|
|
|
#[error(transparent)] |
|
|
|
@ -70,8 +136,8 @@ impl<T> Entry<T> {
@@ -70,8 +136,8 @@ impl<T> Entry<T> {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Entry<ServerInfo> { |
|
|
|
|
fn matches(&self, addr: SocketAddrV4, region: Region, filter: &Filter) -> bool { |
|
|
|
|
self.region == region && filter.matches(addr, &self.value) |
|
|
|
|
fn matches(&self, addr: Addr, region: Region, filter: &Filter) -> bool { |
|
|
|
|
self.region == region && filter.matches(wrap_addr(addr), &self.value) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -106,9 +172,9 @@ impl Counter {
@@ -106,9 +172,9 @@ impl Counter {
|
|
|
|
|
|
|
|
|
|
pub struct MasterServer { |
|
|
|
|
sock: UdpSocket, |
|
|
|
|
challenges: HashMap<SocketAddrV4, Entry<u32>>, |
|
|
|
|
challenges: HashMap<Addr, Entry<u32>>, |
|
|
|
|
challenges_counter: Counter, |
|
|
|
|
servers: HashMap<SocketAddrV4, Entry<ServerInfo>>, |
|
|
|
|
servers: HashMap<Addr, Entry<ServerInfo>>, |
|
|
|
|
servers_counter: Counter, |
|
|
|
|
max_servers_per_ip: u16, |
|
|
|
|
rng: Rng, |
|
|
|
@ -119,39 +185,38 @@ pub struct MasterServer {
@@ -119,39 +185,38 @@ pub struct MasterServer {
|
|
|
|
|
clver: Version, |
|
|
|
|
update_title: Box<str>, |
|
|
|
|
update_map: Box<str>, |
|
|
|
|
update_addr: SocketAddrV4, |
|
|
|
|
update_addr: SocketAddr, |
|
|
|
|
|
|
|
|
|
admin_challenges: HashMap<Ipv4Addr, Entry<(u32, u32)>>, |
|
|
|
|
admin_challenges: HashMap<Ip, Entry<(u32, u32)>>, |
|
|
|
|
admin_challenges_counter: Counter, |
|
|
|
|
admin_list: Box<[config::AdminConfig]>, |
|
|
|
|
// rate limit if hash is invalid
|
|
|
|
|
admin_limit: HashMap<Ipv4Addr, Entry<()>>, |
|
|
|
|
admin_limit: HashMap<Ip, Entry<()>>, |
|
|
|
|
admin_limit_counter: Counter, |
|
|
|
|
hash: config::HashConfig, |
|
|
|
|
|
|
|
|
|
blocklist: HashSet<Ipv4Addr>, |
|
|
|
|
blocklist: HashSet<Ip>, |
|
|
|
|
|
|
|
|
|
stats: Stats, |
|
|
|
|
|
|
|
|
|
// temporary data
|
|
|
|
|
filtered_servers: Vec<SocketAddrV4>, |
|
|
|
|
filtered_servers_nat: Vec<SocketAddrV4>, |
|
|
|
|
filtered_servers: Vec<Addr>, |
|
|
|
|
filtered_servers_nat: Vec<Addr>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn resolve_socket_addr<A>(addr: A) -> io::Result<Option<SocketAddrV4>> |
|
|
|
|
fn resolve_socket_addr<A>(addr: A, is_ipv4: bool) -> io::Result<Option<SocketAddr>> |
|
|
|
|
where |
|
|
|
|
A: ToSocketAddrs, |
|
|
|
|
{ |
|
|
|
|
for i in addr.to_socket_addrs()? { |
|
|
|
|
match i { |
|
|
|
|
SocketAddr::V4(i) => return Ok(Some(i)), |
|
|
|
|
SocketAddr::V6(_) => {} |
|
|
|
|
if i.is_ipv4() == is_ipv4 { |
|
|
|
|
return Ok(Some(i)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(None) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddrV4 { |
|
|
|
|
fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddr { |
|
|
|
|
if let Some(s) = cfg.client.update_addr.as_deref() { |
|
|
|
|
let addr = if !s.contains(':') { |
|
|
|
|
format!("{}:{}", s, local_addr.port()) |
|
|
|
@ -159,22 +224,21 @@ fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddrV4 {
@@ -159,22 +224,21 @@ fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddrV4 {
|
|
|
|
|
s.to_owned() |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
match resolve_socket_addr(&addr) { |
|
|
|
|
match resolve_socket_addr(&addr, local_addr.is_ipv4()) { |
|
|
|
|
Ok(Some(x)) => return x, |
|
|
|
|
Ok(None) => error!("Update address: failed to resolve IPv4 for \"{}\"", addr), |
|
|
|
|
Ok(None) => error!("Update address: failed to resolve IP for \"{}\"", addr), |
|
|
|
|
Err(e) => error!("Update address: {}", e), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match local_addr { |
|
|
|
|
SocketAddr::V4(x) => x, |
|
|
|
|
SocketAddr::V6(_) => todo!(), |
|
|
|
|
} |
|
|
|
|
local_addr |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl MasterServer { |
|
|
|
|
pub fn new(cfg: Config) -> Result<Self, Error> { |
|
|
|
|
let addr = SocketAddr::new(cfg.server.ip, cfg.server.port); |
|
|
|
|
if !check_addr(&addr) { |
|
|
|
|
return Err(Error::Unsupported); |
|
|
|
|
} |
|
|
|
|
info!("Listen address: {}", addr); |
|
|
|
|
let sock = UdpSocket::bind(addr).map_err(Error::BindSocket)?; |
|
|
|
|
// make socket interruptable by singals
|
|
|
|
@ -246,10 +310,12 @@ impl MasterServer {
@@ -246,10 +310,12 @@ impl MasterServer {
|
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let from = match from { |
|
|
|
|
SocketAddr::V4(a) => a, |
|
|
|
|
_ => { |
|
|
|
|
warn!("{}: Received message from IPv6, unimplemented", from); |
|
|
|
|
let from = match extract_addr(from) { |
|
|
|
|
Some(from) => from, |
|
|
|
|
None => { |
|
|
|
|
if from.is_ipv6() { |
|
|
|
|
debug!("{}: IPv6 is not implemented", from); |
|
|
|
|
} |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
@ -271,7 +337,7 @@ impl MasterServer {
@@ -271,7 +337,7 @@ impl MasterServer {
|
|
|
|
|
self.stats.clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn handle_server_packet(&mut self, from: SocketAddrV4, p: server::Packet) -> Result<(), Error> { |
|
|
|
|
fn handle_server_packet(&mut self, from: Addr, p: server::Packet) -> Result<(), Error> { |
|
|
|
|
trace!("{}: recv {:?}", from, p); |
|
|
|
|
|
|
|
|
|
match p { |
|
|
|
@ -320,13 +386,20 @@ impl MasterServer {
@@ -320,13 +386,20 @@ impl MasterServer {
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn handle_game_packet(&mut self, from: SocketAddrV4, p: game::Packet) -> Result<(), Error> { |
|
|
|
|
fn handle_game_packet(&mut self, from: Addr, p: game::Packet) -> Result<(), Error> { |
|
|
|
|
trace!("{}: recv {:?}", from, p); |
|
|
|
|
|
|
|
|
|
match p { |
|
|
|
|
game::Packet::QueryServers(p) => { |
|
|
|
|
if p.filter.clver.map_or(false, |v| v < self.clver) { |
|
|
|
|
self.send_server_list(from, p.filter.key, &[self.update_addr])?; |
|
|
|
|
match self.update_addr { |
|
|
|
|
SocketAddr::V4(addr) => { |
|
|
|
|
self.send_server_list(from, p.filter.key, &[addr])?; |
|
|
|
|
} |
|
|
|
|
SocketAddr::V6(addr) => { |
|
|
|
|
self.send_server_list(from, p.filter.key, &[addr])?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
let now = self.now(); |
|
|
|
|
|
|
|
|
@ -376,7 +449,7 @@ impl MasterServer {
@@ -376,7 +449,7 @@ impl MasterServer {
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn handle_admin_packet(&mut self, from: SocketAddrV4, p: admin::Packet) -> Result<(), Error> { |
|
|
|
|
fn handle_admin_packet(&mut self, from: Addr, p: admin::Packet) -> Result<(), Error> { |
|
|
|
|
trace!("{}: recv {:?}", from, p); |
|
|
|
|
|
|
|
|
|
let now = self.now(); |
|
|
|
@ -449,7 +522,7 @@ impl MasterServer {
@@ -449,7 +522,7 @@ impl MasterServer {
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn handle_packet(&mut self, from: SocketAddrV4, src: &[u8]) -> Result<(), Error> { |
|
|
|
|
fn handle_packet(&mut self, from: Addr, src: &[u8]) -> Result<(), Error> { |
|
|
|
|
if self.is_blocked(from.ip()) { |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
@ -479,7 +552,7 @@ impl MasterServer {
@@ -479,7 +552,7 @@ impl MasterServer {
|
|
|
|
|
self.start_time.elapsed().as_secs() as u32 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn add_challenge(&mut self, addr: SocketAddrV4) -> u32 { |
|
|
|
|
fn add_challenge(&mut self, addr: Addr) -> u32 { |
|
|
|
|
let x = self.rng.u32(..); |
|
|
|
|
let entry = Entry::new(self.now(), x); |
|
|
|
|
self.challenges.insert(addr, entry); |
|
|
|
@ -494,7 +567,7 @@ impl MasterServer {
@@ -494,7 +567,7 @@ impl MasterServer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn admin_challenge_add(&mut self, addr: SocketAddrV4) -> (u32, u32) { |
|
|
|
|
fn admin_challenge_add(&mut self, addr: Addr) -> (u32, u32) { |
|
|
|
|
let x = self.rng.u32(..); |
|
|
|
|
let y = self.rng.u32(..); |
|
|
|
|
let entry = Entry::new(self.now(), (x, y)); |
|
|
|
@ -502,7 +575,7 @@ impl MasterServer {
@@ -502,7 +575,7 @@ impl MasterServer {
|
|
|
|
|
(x, y) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn admin_challenge_remove(&mut self, addr: SocketAddrV4) { |
|
|
|
|
fn admin_challenge_remove(&mut self, addr: Addr) { |
|
|
|
|
self.admin_challenges.remove(addr.ip()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -523,11 +596,11 @@ impl MasterServer {
@@ -523,11 +596,11 @@ impl MasterServer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn count_servers(&self, addr: &Ipv4Addr) -> u16 { |
|
|
|
|
self.servers.keys().filter(|i| i.ip() == addr).count() as u16 |
|
|
|
|
fn count_servers(&self, ip: &Ip) -> u16 { |
|
|
|
|
self.servers.keys().filter(|i| i.ip() == ip).count() as u16 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn add_server(&mut self, addr: SocketAddrV4, server: ServerInfo) { |
|
|
|
|
fn add_server(&mut self, addr: Addr, server: ServerInfo) { |
|
|
|
|
let now = self.now(); |
|
|
|
|
match self.servers.entry(addr) { |
|
|
|
|
hash_map::Entry::Occupied(mut e) => { |
|
|
|
@ -554,34 +627,25 @@ impl MasterServer {
@@ -554,34 +627,25 @@ impl MasterServer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn send_server_list<A>( |
|
|
|
|
&self, |
|
|
|
|
to: A, |
|
|
|
|
key: Option<u32>, |
|
|
|
|
servers: &[SocketAddrV4], |
|
|
|
|
) -> Result<(), Error> |
|
|
|
|
fn send_server_list<A, S>(&self, to: A, key: Option<u32>, servers: &[S]) -> Result<(), Error> |
|
|
|
|
where |
|
|
|
|
A: ToSocketAddrs, |
|
|
|
|
S: ServerAddress, |
|
|
|
|
{ |
|
|
|
|
let mut list = master::QueryServersResponse::new(servers.iter().copied(), key); |
|
|
|
|
loop { |
|
|
|
|
let mut buf = [0; MAX_PACKET_SIZE]; |
|
|
|
|
let (n, is_end) = list.encode(&mut buf)?; |
|
|
|
|
let mut offset = 0; |
|
|
|
|
let mut list = master::QueryServersResponse::new(key); |
|
|
|
|
while offset < servers.len() { |
|
|
|
|
let (n, c) = list.encode(&mut buf, &servers[offset..])?; |
|
|
|
|
offset += c; |
|
|
|
|
self.sock.send_to(&buf[..n], &to)?; |
|
|
|
|
if is_end { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn send_client_to_nat_servers( |
|
|
|
|
&self, |
|
|
|
|
to: SocketAddrV4, |
|
|
|
|
servers: &[SocketAddrV4], |
|
|
|
|
) -> Result<(), Error> { |
|
|
|
|
fn send_client_to_nat_servers(&self, to: Addr, servers: &[Addr]) -> Result<(), Error> { |
|
|
|
|
let mut buf = [0; 64]; |
|
|
|
|
let n = master::ClientAnnounce::new(to).encode(&mut buf)?; |
|
|
|
|
let n = master::ClientAnnounce::new(wrap_addr(to)).encode(&mut buf)?; |
|
|
|
|
let buf = &buf[..n]; |
|
|
|
|
for i in servers { |
|
|
|
|
self.sock.send_to(buf, i)?; |
|
|
|
@ -590,15 +654,15 @@ impl MasterServer {
@@ -590,15 +654,15 @@ impl MasterServer {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[inline] |
|
|
|
|
fn is_blocked(&self, ip: &Ipv4Addr) -> bool { |
|
|
|
|
fn is_blocked(&self, ip: &Ip) -> bool { |
|
|
|
|
self.blocklist.contains(ip) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn admin_command(&mut self, cmd: &str) { |
|
|
|
|
let args: Vec<_> = cmd.split(' ').collect(); |
|
|
|
|
|
|
|
|
|
fn helper<F: FnMut(&str, Ipv4Addr)>(args: &[&str], mut op: F) { |
|
|
|
|
let iter = args.iter().map(|i| (i, i.parse::<Ipv4Addr>())); |
|
|
|
|
fn helper<F: FnMut(&str, Ip)>(args: &[&str], mut op: F) { |
|
|
|
|
let iter = args.iter().map(|i| (i, i.parse::<Ip>())); |
|
|
|
|
for (i, ip) in iter { |
|
|
|
|
match ip { |
|
|
|
|
Ok(ip) => op(i, ip), |
|
|
|
|