Browse Source

add basic ipv6 support

ipv6
Denis Drakhnia 3 months ago
parent
commit
f7de9f6f0d
  1. 1
      master/Cargo.toml
  2. 9
      master/src/config.rs
  3. 10
      master/src/main.rs
  4. 204
      master/src/master_server.rs
  5. 4
      master/src/stats/stub.rs
  6. 8
      protocol/src/filter.rs
  7. 10
      protocol/src/game.rs
  8. 148
      protocol/src/master.rs

1
master/Cargo.toml

@ -15,6 +15,7 @@ repository = "https://git.mentality.rip/numas13/xash3d-master"
default = ["logtime"] default = ["logtime"]
logtime = ["chrono"] logtime = ["chrono"]
stats = [] stats = []
ipv6 = []
[dependencies] [dependencies]
thiserror = "1.0.49" thiserror = "1.0.49"

9
master/src/config.rs

@ -3,7 +3,7 @@
use std::fs; use std::fs;
use std::io; use std::io;
use std::net::{IpAddr, Ipv4Addr}; use std::net::IpAddr;
use std::path::Path; use std::path::Path;
use log::LevelFilter; use log::LevelFilter;
@ -12,7 +12,12 @@ use thiserror::Error;
use xash3d_protocol::admin; use xash3d_protocol::admin;
use xash3d_protocol::filter::Version; use xash3d_protocol::filter::Version;
pub const DEFAULT_MASTER_SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); #[cfg(not(feature = "ipv6"))]
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;
pub const DEFAULT_SERVER_TIMEOUT: u32 = 300; pub const DEFAULT_SERVER_TIMEOUT: u32 = 300;

10
master/src/main.rs

@ -9,9 +9,13 @@ mod logger;
mod master_server; mod master_server;
mod stats; mod stats;
use std::process; use std::{
use std::sync::atomic::{AtomicBool, Ordering}; process,
use std::sync::Arc; sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use log::{error, info}; use log::{error, info};
#[cfg(not(windows))] #[cfg(not(windows))]

204
master/src/master_server.rs

@ -1,25 +1,89 @@
// 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::collections::hash_map; use std::{
use std::io; collections::hash_map,
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket}; io,
use std::ops::Deref; net::{SocketAddr, ToSocketAddrs, UdpSocket},
use std::sync::atomic::{AtomicBool, Ordering}; ops::Deref,
use std::time::{Duration, Instant}; sync::atomic::{AtomicBool, Ordering},
time::{Duration, Instant},
};
use ahash::{AHashMap as HashMap, AHashSet as HashSet}; use ahash::{AHashMap as HashMap, AHashSet as HashSet};
use blake2b_simd::Params; use blake2b_simd::Params;
use fastrand::Rng; use fastrand::Rng;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use thiserror::Error; use thiserror::Error;
use xash3d_protocol::filter::{Filter, FilterFlags, Version}; use xash3d_protocol::{
use xash3d_protocol::server::Region; admin,
use xash3d_protocol::wrappers::Str; filter::{Filter, FilterFlags, Version},
use xash3d_protocol::{admin, game, master, server, Error as ProtocolError, ServerInfo}; 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()
}
}
#[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
}
}
use crate::config::{self, Config}; #[inline(always)]
use crate::stats::Stats; 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. /// The maximum size of UDP packets.
const MAX_PACKET_SIZE: usize = 512; const MAX_PACKET_SIZE: usize = 512;
@ -40,6 +104,8 @@ 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)]
@ -70,8 +136,8 @@ impl<T> Entry<T> {
} }
impl Entry<ServerInfo> { impl Entry<ServerInfo> {
fn matches(&self, addr: SocketAddrV4, region: Region, filter: &Filter) -> bool { fn matches(&self, addr: Addr, region: Region, filter: &Filter) -> bool {
self.region == region && filter.matches(addr, &self.value) self.region == region && filter.matches(wrap_addr(addr), &self.value)
} }
} }
@ -106,9 +172,9 @@ impl Counter {
pub struct MasterServer { pub struct MasterServer {
sock: UdpSocket, sock: UdpSocket,
challenges: HashMap<SocketAddrV4, Entry<u32>>, challenges: HashMap<Addr, Entry<u32>>,
challenges_counter: Counter, challenges_counter: Counter,
servers: HashMap<SocketAddrV4, Entry<ServerInfo>>, servers: HashMap<Addr, Entry<ServerInfo>>,
servers_counter: Counter, servers_counter: Counter,
max_servers_per_ip: u16, max_servers_per_ip: u16,
rng: Rng, rng: Rng,
@ -119,39 +185,38 @@ pub struct MasterServer {
clver: Version, clver: Version,
update_title: Box<str>, update_title: Box<str>,
update_map: 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_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<Ipv4Addr, Entry<()>>, admin_limit: HashMap<Ip, Entry<()>>,
admin_limit_counter: Counter, admin_limit_counter: Counter,
hash: config::HashConfig, hash: config::HashConfig,
blocklist: HashSet<Ipv4Addr>, blocklist: HashSet<Ip>,
stats: Stats, stats: Stats,
// temporary data // temporary data
filtered_servers: Vec<SocketAddrV4>, filtered_servers: Vec<Addr>,
filtered_servers_nat: Vec<SocketAddrV4>, 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 where
A: ToSocketAddrs, A: ToSocketAddrs,
{ {
for i in addr.to_socket_addrs()? { for i in addr.to_socket_addrs()? {
match i { if i.is_ipv4() == is_ipv4 {
SocketAddr::V4(i) => return Ok(Some(i)), return Ok(Some(i));
SocketAddr::V6(_) => {}
} }
} }
Ok(None) 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() { if let Some(s) = cfg.client.update_addr.as_deref() {
let addr = if !s.contains(':') { let addr = if !s.contains(':') {
format!("{}:{}", s, local_addr.port()) format!("{}:{}", s, local_addr.port())
@ -159,22 +224,21 @@ fn resolve_update_addr(cfg: &Config, local_addr: SocketAddr) -> SocketAddrV4 {
s.to_owned() s.to_owned()
}; };
match resolve_socket_addr(&addr) { match resolve_socket_addr(&addr, local_addr.is_ipv4()) {
Ok(Some(x)) => return x, 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), Err(e) => error!("Update address: {}", e),
} }
} }
local_addr
match local_addr {
SocketAddr::V4(x) => x,
SocketAddr::V6(_) => todo!(),
}
} }
impl MasterServer { impl MasterServer {
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); let addr = SocketAddr::new(cfg.server.ip, cfg.server.port);
if !check_addr(&addr) {
return Err(Error::Unsupported);
}
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
@ -246,10 +310,12 @@ impl MasterServer {
}, },
}; };
let from = match from { let from = match extract_addr(from) {
SocketAddr::V4(a) => a, Some(from) => from,
_ => { None => {
warn!("{}: Received message from IPv6, unimplemented", from); if from.is_ipv6() {
debug!("{}: IPv6 is not implemented", from);
}
continue; continue;
} }
}; };
@ -271,7 +337,7 @@ impl MasterServer {
self.stats.clear(); 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); trace!("{}: recv {:?}", from, p);
match p { match p {
@ -320,13 +386,20 @@ impl MasterServer {
Ok(()) 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); trace!("{}: recv {:?}", from, p);
match p { match p {
game::Packet::QueryServers(p) => { game::Packet::QueryServers(p) => {
if p.filter.clver.map_or(false, |v| v < self.clver) { 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 { } else {
let now = self.now(); let now = self.now();
@ -376,7 +449,7 @@ impl MasterServer {
Ok(()) 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); trace!("{}: recv {:?}", from, p);
let now = self.now(); let now = self.now();
@ -449,7 +522,7 @@ impl MasterServer {
Ok(()) 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()) { if self.is_blocked(from.ip()) {
return Ok(()); return Ok(());
} }
@ -479,7 +552,7 @@ impl MasterServer {
self.start_time.elapsed().as_secs() as u32 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 x = self.rng.u32(..);
let entry = Entry::new(self.now(), x); let entry = Entry::new(self.now(), x);
self.challenges.insert(addr, entry); self.challenges.insert(addr, entry);
@ -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 x = self.rng.u32(..);
let y = self.rng.u32(..); let y = self.rng.u32(..);
let entry = Entry::new(self.now(), (x, y)); let entry = Entry::new(self.now(), (x, y));
@ -502,7 +575,7 @@ impl MasterServer {
(x, y) (x, y)
} }
fn admin_challenge_remove(&mut self, addr: SocketAddrV4) { fn admin_challenge_remove(&mut self, addr: Addr) {
self.admin_challenges.remove(addr.ip()); self.admin_challenges.remove(addr.ip());
} }
@ -523,11 +596,11 @@ impl MasterServer {
} }
} }
fn count_servers(&self, addr: &Ipv4Addr) -> u16 { fn count_servers(&self, ip: &Ip) -> u16 {
self.servers.keys().filter(|i| i.ip() == addr).count() as 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(); let now = self.now();
match self.servers.entry(addr) { match self.servers.entry(addr) {
hash_map::Entry::Occupied(mut e) => { hash_map::Entry::Occupied(mut e) => {
@ -554,34 +627,25 @@ impl MasterServer {
} }
} }
fn send_server_list<A>( fn send_server_list<A, S>(&self, to: A, key: Option<u32>, servers: &[S]) -> Result<(), Error>
&self,
to: A,
key: Option<u32>,
servers: &[SocketAddrV4],
) -> Result<(), Error>
where where
A: ToSocketAddrs, A: ToSocketAddrs,
S: ServerAddress,
{ {
let mut list = master::QueryServersResponse::new(servers.iter().copied(), key);
loop {
let mut buf = [0; MAX_PACKET_SIZE]; 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)?; self.sock.send_to(&buf[..n], &to)?;
if is_end {
break;
}
} }
Ok(()) Ok(())
} }
fn send_client_to_nat_servers( fn send_client_to_nat_servers(&self, to: Addr, servers: &[Addr]) -> Result<(), Error> {
&self,
to: SocketAddrV4,
servers: &[SocketAddrV4],
) -> Result<(), Error> {
let mut buf = [0; 64]; 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]; let buf = &buf[..n];
for i in servers { for i in servers {
self.sock.send_to(buf, i)?; self.sock.send_to(buf, i)?;
@ -590,15 +654,15 @@ impl MasterServer {
} }
#[inline] #[inline]
fn is_blocked(&self, ip: &Ipv4Addr) -> bool { fn is_blocked(&self, ip: &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, Ipv4Addr)>(args: &[&str], mut op: F) { fn helper<F: FnMut(&str, Ip)>(args: &[&str], mut op: F) {
let iter = args.iter().map(|i| (i, i.parse::<Ipv4Addr>())); let iter = args.iter().map(|i| (i, i.parse::<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),

4
master/src/stats/stub.rs

@ -6,7 +6,9 @@ struct Counters;
pub struct Stats; pub struct Stats;
impl Stats { impl Stats {
pub fn new(_: StatConfig) -> Self { Self } pub fn new(_: StatConfig) -> Self {
Self
}
pub fn update_config(&mut self, _: StatConfig) {} pub fn update_config(&mut self, _: StatConfig) {}
pub fn clear(&self) {} pub fn clear(&self) {}
pub fn servers_count(&self, _: usize) {} pub fn servers_count(&self, _: usize) {}

8
protocol/src/filter.rs

@ -30,7 +30,7 @@
//! * Is not protected by a password //! * Is not protected by a password
use std::fmt; use std::fmt;
use std::net::SocketAddrV4; use std::net::SocketAddr;
use std::str::FromStr; use std::str::FromStr;
use bitflags::bitflags; use bitflags::bitflags;
@ -196,7 +196,8 @@ impl Filter<'_> {
} }
/// Returns `true` if a server matches the filter. /// Returns `true` if a server matches the filter.
pub fn matches(&self, _addr: SocketAddrV4, info: &ServerInfo) -> bool { pub fn matches(&self, _addr: SocketAddr, info: &ServerInfo) -> bool {
// TODO: match addr
!((info.flags & self.flags_mask) != self.flags !((info.flags & self.flags_mask) != self.flags
|| self.gamedir.map_or(false, |s| *s != &*info.gamedir) || self.gamedir.map_or(false, |s| *s != &*info.gamedir)
|| self.map.map_or(false, |s| *s != &*info.map) || self.map.map_or(false, |s| *s != &*info.map)
@ -308,6 +309,7 @@ mod tests {
use super::*; use super::*;
use crate::cursor::CursorMut; use crate::cursor::CursorMut;
use crate::wrappers::Str; use crate::wrappers::Str;
use std::net::SocketAddr;
macro_rules! tests { macro_rules! tests {
($($name:ident$(($($predefined_f:ident: $predefined_v:expr),+ $(,)?))? { ($($name:ident$(($($predefined_f:ident: $predefined_v:expr),+ $(,)?))? {
@ -450,7 +452,7 @@ mod tests {
macro_rules! servers { macro_rules! servers {
($($addr:expr => $info:expr $(=> $func:expr)?)+) => ( ($($addr:expr => $info:expr $(=> $func:expr)?)+) => (
[$({ [$({
let addr = $addr.parse::<SocketAddrV4>().unwrap(); let addr = $addr.parse::<SocketAddr>().unwrap();
let mut buf = [0; 512]; let mut buf = [0; 512];
let n = CursorMut::new(&mut buf) let n = CursorMut::new(&mut buf)
.put_bytes(ServerAdd::HEADER).unwrap() .put_bytes(ServerAdd::HEADER).unwrap()

10
protocol/src/game.rs

@ -4,7 +4,7 @@
//! Game client packets. //! Game client packets.
use std::fmt; use std::fmt;
use std::net::SocketAddrV4; use std::net::SocketAddr;
use crate::cursor::{Cursor, CursorMut}; use crate::cursor::{Cursor, CursorMut};
use crate::filter::Filter; use crate::filter::Filter;
@ -17,7 +17,7 @@ pub struct QueryServers<T> {
/// Servers must be from the `region`. /// Servers must be from the `region`.
pub region: Region, pub region: Region,
/// Last received server address __(not used)__. /// Last received server address __(not used)__.
pub last: SocketAddrV4, pub last: SocketAddr,
/// Select only servers that match the `filter`. /// Select only servers that match the `filter`.
pub filter: T, pub filter: T,
} }
@ -131,13 +131,13 @@ mod tests {
use super::*; use super::*;
use crate::filter::{FilterFlags, Version}; use crate::filter::{FilterFlags, Version};
use crate::wrappers::Str; use crate::wrappers::Str;
use std::net::Ipv4Addr; use std::net::{IpAddr, Ipv4Addr};
#[test] #[test]
fn query_servers() { fn query_servers() {
let p = QueryServers { let p = QueryServers {
region: Region::RestOfTheWorld, region: Region::RestOfTheWorld,
last: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0), last: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
filter: Filter { filter: Filter {
gamedir: Some(Str(&b"valve"[..])), gamedir: Some(Str(&b"valve"[..])),
map: Some(Str(&b"crossfire"[..])), map: Some(Str(&b"crossfire"[..])),
@ -157,7 +157,7 @@ mod tests {
fn query_servers_filter_bug() { fn query_servers_filter_bug() {
let p = QueryServers { let p = QueryServers {
region: Region::RestOfTheWorld, region: Region::RestOfTheWorld,
last: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0), last: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
filter: Filter { filter: Filter {
gamedir: None, gamedir: None,
protocol: Some(48), protocol: Some(48),

148
protocol/src/master.rs

@ -3,7 +3,7 @@
//! Master server packets. //! Master server packets.
use std::net::{Ipv4Addr, SocketAddrV4}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use super::cursor::{Cursor, CursorMut}; use super::cursor::{Cursor, CursorMut};
use super::Error; use super::Error;
@ -58,6 +58,54 @@ impl ChallengeResponse {
} }
} }
/// Helper trait for dealing with server addresses.
pub trait ServerAddress: Sized {
/// Size of IP and port in bytes.
fn size() -> usize;
/// Read address from a cursor.
fn get(cur: &mut Cursor) -> Result<Self, Error>;
/// Write address to a cursor.
fn put(&self, cur: &mut CursorMut) -> Result<(), Error>;
}
impl ServerAddress for SocketAddrV4 {
fn size() -> usize {
6
}
fn get(cur: &mut Cursor) -> Result<Self, Error> {
let ip = Ipv4Addr::from(cur.get_array()?);
let port = cur.get_u16_be()?;
Ok(SocketAddrV4::new(ip, port))
}
fn put(&self, cur: &mut CursorMut) -> Result<(), Error> {
cur.put_array(&self.ip().octets())?;
cur.put_u16_be(self.port())?;
Ok(())
}
}
impl ServerAddress for SocketAddrV6 {
fn size() -> usize {
18
}
fn get(cur: &mut Cursor) -> Result<Self, Error> {
let ip = Ipv6Addr::from(cur.get_array()?);
let port = cur.get_u16_be()?;
Ok(SocketAddrV6::new(ip, port, 0, 0))
}
fn put(&self, cur: &mut CursorMut) -> Result<(), Error> {
cur.put_array(&self.ip().octets())?;
cur.put_u16_be(self.port())?;
Ok(())
}
}
/// Game server addresses list. /// Game server addresses list.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct QueryServersResponse<I> { pub struct QueryServersResponse<I> {
@ -76,33 +124,31 @@ impl<'a> QueryServersResponse<&'a [u8]> {
pub fn decode(src: &'a [u8]) -> Result<Self, Error> { pub fn decode(src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(QueryServersResponse::HEADER)?; cur.expect(QueryServersResponse::HEADER)?;
if cur.remaining() % 6 != 0 { let s = cur.end();
return Err(Error::InvalidPacket);
}
let s = cur.get_bytes(cur.remaining())?;
// extra header for key sent in QueryServers packet // extra header for key sent in QueryServers packet
let (s, key) = if s.len() >= 6 && s[0] == 0x7f && s[5] == 8 { let (inner, key) = if s.len() >= 6 && s[0] == 0x7f && s[5] == 8 {
(&s[6..], Some(u32::from_le_bytes([s[1], s[2], s[3], s[4]]))) let key = u32::from_le_bytes([s[1], s[2], s[3], s[4]]);
(&s[6..], Some(key))
} else { } else {
(s, None) (s, None)
}; };
let inner = if s.ends_with(&[0; 6]) {
&s[..s.len() - 6]
} else {
s
};
Ok(Self { inner, key }) Ok(Self { inner, key })
} }
/// Iterator over game server addresses. /// Iterator over game server addresses.
pub fn iter(&self) -> impl 'a + Iterator<Item = SocketAddrV4> { pub fn iter<A>(&self) -> impl 'a + Iterator<Item = A>
where
A: ServerAddress,
{
let mut cur = Cursor::new(self.inner); let mut cur = Cursor::new(self.inner);
(0..self.inner.len() / 6).map(move |_| { std::iter::from_fn(move || {
let ip = Ipv4Addr::from(cur.get_array().unwrap()); if cur.remaining() == A::size() && cur.end().ends_with(&[0; 2]) {
let port = cur.get_u16_be().unwrap(); // skip last address with port 0
SocketAddrV4::new(ip, port) return None;
}
A::get(&mut cur).ok()
}) })
} }
@ -112,13 +158,10 @@ impl<'a> QueryServersResponse<&'a [u8]> {
} }
} }
impl<I> QueryServersResponse<I> impl QueryServersResponse<()> {
where
I: Iterator<Item = SocketAddrV4>,
{
/// Creates a new `QueryServersResponse`. /// Creates a new `QueryServersResponse`.
pub fn new(iter: I, key: Option<u32>) -> Self { pub fn new(key: Option<u32>) -> Self {
Self { inner: iter, key } Self { inner: (), key }
} }
/// Encode packet to `buf`. /// Encode packet to `buf`.
@ -127,7 +170,10 @@ where
/// multiple times until the end flag equals `true`. /// multiple times until the end flag equals `true`.
/// ///
/// Returns how many bytes was written in `buf` and the end flag. /// Returns how many bytes was written in `buf` and the end flag.
pub fn encode(&mut self, buf: &mut [u8]) -> Result<(usize, bool), Error> { pub fn encode<A>(&mut self, buf: &mut [u8], list: &[A]) -> Result<(usize, usize), Error>
where
A: ServerAddress,
{
let mut cur = CursorMut::new(buf); let mut cur = CursorMut::new(buf);
cur.put_bytes(QueryServersResponse::HEADER)?; cur.put_bytes(QueryServersResponse::HEADER)?;
if let Some(key) = self.key { if let Some(key) = self.key {
@ -135,19 +181,20 @@ where
cur.put_u32_le(key)?; cur.put_u32_le(key)?;
cur.put_u8(8)?; cur.put_u8(8)?;
} }
let mut is_end = false; let mut count = 0;
while cur.remaining() >= 12 { let mut iter = list.iter();
match self.inner.next() { while cur.remaining() >= A::size() * 2 {
Some(i) => { if let Some(i) = iter.next() {
cur.put_array(&i.ip().octets())?.put_u16_be(i.port())?; i.put(&mut cur)?;
} count += 1;
None => { } else {
is_end = true;
break; break;
} }
} }
for _ in 0..A::size() {
cur.put_u8(0)?;
} }
Ok((cur.put_array(&[0; 6])?.pos(), is_end)) Ok((cur.pos(), count))
} }
} }
@ -155,7 +202,7 @@ where
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ClientAnnounce { pub struct ClientAnnounce {
/// Address of the client. /// Address of the client.
pub addr: SocketAddrV4, pub addr: SocketAddr,
} }
impl ClientAnnounce { impl ClientAnnounce {
@ -163,7 +210,7 @@ impl ClientAnnounce {
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffc "; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffc ";
/// Creates a new `ClientAnnounce`. /// Creates a new `ClientAnnounce`.
pub fn new(addr: SocketAddrV4) -> Self { pub fn new(addr: SocketAddr) -> Self {
Self { addr } Self { addr }
} }
@ -296,18 +343,39 @@ mod tests {
} }
#[test] #[test]
fn query_servers_response() { fn query_servers_response_ipv4() {
let servers: &[SocketAddrV4] = &[ type Addr = SocketAddrV4;
let servers: &[Addr] = &[
"1.2.3.4:27001".parse().unwrap(), "1.2.3.4:27001".parse().unwrap(),
"1.2.3.4:27002".parse().unwrap(), "1.2.3.4:27002".parse().unwrap(),
"1.2.3.4:27003".parse().unwrap(), "1.2.3.4:27003".parse().unwrap(),
"1.2.3.4:27004".parse().unwrap(), "1.2.3.4:27004".parse().unwrap(),
]; ];
let mut p = QueryServersResponse::new(servers.iter().cloned(), Some(0xdeadbeef)); let mut p = QueryServersResponse::new(Some(0xdeadbeef));
let mut buf = [0; 512];
let (n, c) = p.encode(&mut buf, servers).unwrap();
assert_eq!(c, servers.len());
assert_eq!(n, 12 + Addr::size() * (servers.len() + 1));
let e = QueryServersResponse::decode(&buf[..n]).unwrap();
assert_eq!(e.iter::<Addr>().collect::<Vec<_>>(), servers);
}
#[test]
fn query_servers_response_ipv6() {
type Addr = SocketAddrV6;
let servers: &[Addr] = &[
"[::1]:27001".parse().unwrap(),
"[::2]:27002".parse().unwrap(),
"[::3]:27003".parse().unwrap(),
"[::4]:27004".parse().unwrap(),
];
let mut p = QueryServersResponse::new(Some(0xdeadbeef));
let mut buf = [0; 512]; let mut buf = [0; 512];
let (n, _) = p.encode(&mut buf).unwrap(); let (n, c) = p.encode(&mut buf, servers).unwrap();
assert_eq!(c, servers.len());
assert_eq!(n, 12 + Addr::size() * (servers.len() + 1));
let e = QueryServersResponse::decode(&buf[..n]).unwrap(); let e = QueryServersResponse::decode(&buf[..n]).unwrap();
assert_eq!(e.iter().collect::<Vec<_>>(), servers); assert_eq!(e.iter::<Addr>().collect::<Vec<_>>(), servers);
} }
#[test] #[test]

Loading…
Cancel
Save