Browse Source

master: Admin rate limit

ipv6
Denis Drakhnia 1 year ago
parent
commit
09e41ac2f3
  1. 9
      admin/src/main.rs
  2. 2
      master/config/main.toml
  3. 8
      master/src/config.rs
  4. 55
      master/src/master_server.rs
  5. 14
      protocol/src/admin.rs
  6. 23
      protocol/src/master.rs

9
admin/src/main.rs

@ -30,8 +30,8 @@ fn send_command(cli: &cli::Cli) -> Result<(), Error> {
sock.send(&buf[..n])?; sock.send(&buf[..n])?;
let n = sock.recv(&mut buf)?; let n = sock.recv(&mut buf)?;
let challenge = match master::Packet::decode(&buf[..n])? { let (master_challenge, hash_challenge) = match master::Packet::decode(&buf[..n])? {
master::Packet::AdminChallengeResponse(p) => p.challenge, master::Packet::AdminChallengeResponse(p) => (p.master_challenge, p.hash_challenge),
_ => return Err(Error::UnexpectedPacket), _ => return Err(Error::UnexpectedPacket),
}; };
@ -54,10 +54,11 @@ fn send_command(cli: &cli::Cli) -> Result<(), Error> {
.personal(cli.hash_personal.as_bytes()) .personal(cli.hash_personal.as_bytes())
.to_state() .to_state()
.update(password.as_bytes()) .update(password.as_bytes())
.update(&challenge.to_le_bytes()) .update(&hash_challenge.to_le_bytes())
.finalize(); .finalize();
let n = admin::AdminCommand::new(hash.as_bytes(), &cli.command).encode(&mut buf)?; let n = admin::AdminCommand::new(master_challenge, hash.as_bytes(), &cli.command)
.encode(&mut buf)?;
sock.send(&buf[..n])?; sock.send(&buf[..n])?;
Ok(()) Ok(())

2
master/config/main.toml

@ -13,6 +13,8 @@ port = 27010
challenge = 300 challenge = 300
# Time in seconds while server is valid # Time in seconds while server is valid
server = 300 server = 300
# TIme in seconds before next admin request is allowed after wrong password
admin = 10
[client] [client]
# If client version is less then show update message # If client version is less then show update message

8
master/src/config.rs

@ -17,6 +17,7 @@ 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_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
pub const DEFAULT_MASTER_SERVER_PORT: u16 = 27010; pub const DEFAULT_MASTER_SERVER_PORT: u16 = 27010;
pub const DEFAULT_TIMEOUT: u32 = 300; pub const DEFAULT_TIMEOUT: u32 = 300;
pub const DEFAULT_ADMIN_TIMEOUT: u32 = 10;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
@ -86,6 +87,8 @@ pub struct TimeoutConfig {
pub challenge: u32, pub challenge: u32,
#[serde(default = "default_timeout")] #[serde(default = "default_timeout")]
pub server: u32, pub server: u32,
#[serde(default = "default_admin_timeout")]
pub admin: u32,
} }
impl Default for TimeoutConfig { impl Default for TimeoutConfig {
@ -93,6 +96,7 @@ impl Default for TimeoutConfig {
Self { Self {
challenge: default_timeout(), challenge: default_timeout(),
server: default_timeout(), server: default_timeout(),
admin: default_admin_timeout(),
} }
} }
} }
@ -145,6 +149,10 @@ fn default_timeout() -> u32 {
DEFAULT_TIMEOUT DEFAULT_TIMEOUT
} }
fn default_admin_timeout() -> u32 {
DEFAULT_ADMIN_TIMEOUT
}
fn default_hash_len() -> usize { fn default_hash_len() -> usize {
admin::HASH_LEN admin::HASH_LEN
} }

55
master/src/master_server.rs

@ -29,6 +29,9 @@ const CHALLENGE_CLEANUP_MAX: usize = 100;
/// How many cleanup calls should be skipped before removing outdated admin challenges. /// How many cleanup calls should be skipped before removing outdated admin challenges.
const ADMIN_CHALLENGE_CLEANUP_MAX: usize = 100; const ADMIN_CHALLENGE_CLEANUP_MAX: usize = 100;
/// How many cleanup calls should be skipped before removing outdated admin limit entries
const ADMIN_LIMIT_CLEANUP_MAX: usize = 100;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[error("Failed to bind server socket: {0}")] #[error("Failed to bind server socket: {0}")]
@ -109,9 +112,12 @@ struct MasterServer {
update_map: Box<str>, update_map: Box<str>,
update_addr: SocketAddrV4, update_addr: SocketAddrV4,
admin_challenges: HashMap<Ipv4Addr, Entry<u32>>, admin_challenges: HashMap<Ipv4Addr, 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
admin_limit: HashMap<Ipv4Addr, Entry<()>>,
admin_limit_counter: Counter,
hash: config::HashConfig, hash: config::HashConfig,
blocklist: HashSet<Ipv4Addr>, blocklist: HashSet<Ipv4Addr>,
@ -146,6 +152,8 @@ impl MasterServer {
admin_challenges: Default::default(), admin_challenges: Default::default(),
admin_challenges_counter: Counter::new(ADMIN_CHALLENGE_CLEANUP_MAX), admin_challenges_counter: Counter::new(ADMIN_CHALLENGE_CLEANUP_MAX),
admin_list: cfg.admin_list, admin_list: cfg.admin_list,
admin_limit: Default::default(),
admin_limit_counter: Counter::new(ADMIN_LIMIT_CLEANUP_MAX),
hash: cfg.hash, hash: cfg.hash,
blocklist: Default::default(), blocklist: Default::default(),
}) })
@ -255,13 +263,21 @@ impl MasterServer {
} }
if let Ok(p) = admin::Packet::decode(self.hash.len, src) { if let Ok(p) = admin::Packet::decode(self.hash.len, src) {
// TODO: throttle let now = self.now();
if let Some(e) = self.admin_limit.get(from.ip()) {
if e.is_valid(now, self.timeout.admin) {
trace!("{}: rate limit", from);
return Ok(());
}
}
match p { match p {
admin::Packet::AdminChallenge(p) => { admin::Packet::AdminChallenge(p) => {
trace!("{}: recv {:?}", from, p); trace!("{}: recv {:?}", from, p);
let challenge = self.admin_challenge_add(from); let (master_challenge, hash_challenge) = self.admin_challenge_add(from);
let p = master::AdminChallengeResponse::new(challenge); let p = master::AdminChallengeResponse::new(master_challenge, hash_challenge);
trace!("{}: send {:?}", from, p); trace!("{}: send {:?}", from, p);
let mut buf = [0; 64]; let mut buf = [0; 64];
let n = p.encode(&mut buf)?; let n = p.encode(&mut buf)?;
@ -271,11 +287,21 @@ impl MasterServer {
} }
admin::Packet::AdminCommand(p) => { admin::Packet::AdminCommand(p) => {
trace!("{}: recv {:?}", from, p); trace!("{}: recv {:?}", from, p);
let challenge = *self let entry = *self
.admin_challenges .admin_challenges
.get(from.ip()) .get(from.ip())
.ok_or(Error::AdminChallengeNotFound)?; .ok_or(Error::AdminChallengeNotFound)?;
if entry.0 != p.master_challenge {
trace!("{}: master challenge is not valid", from);
return Ok(());
}
if !entry.is_valid(now, self.timeout.challenge) {
trace!("{}: challenge is outdated", from);
return Ok(());
}
let state = Params::new() let state = Params::new()
.hash_length(self.hash.len) .hash_length(self.hash.len)
.key(self.hash.key.as_bytes()) .key(self.hash.key.as_bytes())
@ -286,7 +312,7 @@ impl MasterServer {
let hash = state let hash = state
.clone() .clone()
.update(i.password.as_bytes()) .update(i.password.as_bytes())
.update(&challenge.to_le_bytes()) .update(&entry.1.to_le_bytes())
.finalize(); .finalize();
*p.hash == hash.as_bytes() *p.hash == hash.as_bytes()
}); });
@ -299,6 +325,8 @@ impl MasterServer {
} }
None => { None => {
warn!("{}: invalid admin hash, command: {:?}", from, p.command); warn!("{}: invalid admin hash, command: {:?}", from, p.command);
self.admin_limit.insert(*from.ip(), Entry::new(now, ()));
self.admin_limit_cleanup();
} }
} }
} }
@ -327,11 +355,12 @@ impl MasterServer {
} }
} }
fn admin_challenge_add(&mut self, addr: SocketAddrV4) -> u32 { fn admin_challenge_add(&mut self, addr: SocketAddrV4) -> (u32, u32) {
let x = self.rng.u32(..); let x = self.rng.u32(..);
let entry = Entry::new(self.now(), x); let y = self.rng.u32(..);
let entry = Entry::new(self.now(), (x, y));
self.admin_challenges.insert(*addr.ip(), entry); self.admin_challenges.insert(*addr.ip(), entry);
x (x, y)
} }
fn admin_challenge_remove(&mut self, addr: SocketAddrV4) { fn admin_challenge_remove(&mut self, addr: SocketAddrV4) {
@ -347,6 +376,14 @@ impl MasterServer {
} }
} }
fn admin_limit_cleanup(&mut self) {
if self.admin_limit_counter.next() {
let now = self.now();
self.admin_limit
.retain(|_, v| v.is_valid(now, self.timeout.admin));
}
}
fn add_server(&mut self, addr: SocketAddrV4, server: ServerInfo) { fn add_server(&mut self, addr: SocketAddrV4, server: ServerInfo) {
match self.servers.insert(addr, Entry::new(self.now(), server)) { match self.servers.insert(addr, Entry::new(self.now(), server)) {
Some(_) => trace!("{}: Updated GameServer", addr), Some(_) => trace!("{}: Updated GameServer", addr),

14
protocol/src/admin.rs

@ -30,6 +30,7 @@ impl AdminChallenge {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AdminCommand<'a> { pub struct AdminCommand<'a> {
pub master_challenge: u32,
pub hash: Hide<&'a [u8]>, pub hash: Hide<&'a [u8]>,
pub command: &'a str, pub command: &'a str,
} }
@ -37,8 +38,9 @@ pub struct AdminCommand<'a> {
impl<'a> AdminCommand<'a> { impl<'a> AdminCommand<'a> {
pub const HEADER: &'static [u8] = b"admin"; pub const HEADER: &'static [u8] = b"admin";
pub fn new(hash: &'a [u8], command: &'a str) -> Self { pub fn new(master_challenge: u32, hash: &'a [u8], command: &'a str) -> Self {
Self { Self {
master_challenge,
hash: Hide(hash), hash: Hide(hash),
command, command,
} }
@ -47,10 +49,15 @@ impl<'a> AdminCommand<'a> {
pub fn decode_with_hash_len(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> { pub fn decode_with_hash_len(hash_len: usize, src: &'a [u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
let master_challenge = cur.get_u32_le()?;
let hash = Hide(cur.get_bytes(hash_len)?); let hash = Hide(cur.get_bytes(hash_len)?);
let command = cur.get_str(cur.remaining())?; let command = cur.get_str(cur.remaining())?;
cur.expect_empty()?; cur.expect_empty()?;
Ok(Self { hash, command }) Ok(Self {
master_challenge,
hash,
command,
})
} }
#[inline] #[inline]
@ -61,6 +68,7 @@ impl<'a> AdminCommand<'a> {
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
.put_u32_le(self.master_challenge)?
.put_bytes(&self.hash)? .put_bytes(&self.hash)?
.put_str(self.command)? .put_str(self.command)?
.pos()) .pos())
@ -101,7 +109,7 @@ mod tests {
#[test] #[test]
fn admin_command() { fn admin_command() {
let p = AdminCommand::new(&[1; HASH_LEN], "foo bar baz"); let p = AdminCommand::new(0x12345678, &[1; HASH_LEN], "foo bar baz");
let mut buf = [0; 512]; let mut buf = [0; 512];
let n = p.encode(&mut buf).unwrap(); let n = p.encode(&mut buf).unwrap();
assert_eq!(AdminCommand::decode(&buf[..n]), Ok(p)); assert_eq!(AdminCommand::decode(&buf[..n]), Ok(p));

23
protocol/src/master.rs

@ -107,28 +107,37 @@ where
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AdminChallengeResponse { pub struct AdminChallengeResponse {
pub challenge: u32, pub master_challenge: u32,
pub hash_challenge: u32,
} }
impl AdminChallengeResponse { impl AdminChallengeResponse {
pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffadminchallenge"; pub const HEADER: &'static [u8] = b"\xff\xff\xff\xffadminchallenge";
pub fn new(challenge: u32) -> Self { pub fn new(master_challenge: u32, hash_challenge: u32) -> Self {
Self { challenge } Self {
master_challenge,
hash_challenge,
}
} }
pub fn decode(src: &[u8]) -> Result<Self, Error> { pub fn decode(src: &[u8]) -> Result<Self, Error> {
let mut cur = Cursor::new(src); let mut cur = Cursor::new(src);
cur.expect(Self::HEADER)?; cur.expect(Self::HEADER)?;
let challenge = cur.get_u32_le()?; let master_challenge = cur.get_u32_le()?;
let hash_challenge = cur.get_u32_le()?;
cur.expect_empty()?; cur.expect_empty()?;
Ok(Self { challenge }) Ok(Self {
master_challenge,
hash_challenge,
})
} }
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> { pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
Ok(CursorMut::new(buf) Ok(CursorMut::new(buf)
.put_bytes(Self::HEADER)? .put_bytes(Self::HEADER)?
.put_u32_le(self.challenge)? .put_u32_le(self.master_challenge)?
.put_u32_le(self.hash_challenge)?
.pos()) .pos())
} }
} }
@ -187,7 +196,7 @@ mod tests {
#[test] #[test]
fn admin_challenge_response() { fn admin_challenge_response() {
let p = AdminChallengeResponse::new(0x12345678); let p = AdminChallengeResponse::new(0x12345678, 0x87654321);
let mut buf = [0; 64]; let mut buf = [0; 64];
let n = p.encode(&mut buf).unwrap(); let n = p.encode(&mut buf).unwrap();
assert_eq!(AdminChallengeResponse::decode(&buf[..n]), Ok(p)); assert_eq!(AdminChallengeResponse::decode(&buf[..n]), Ok(p));

Loading…
Cancel
Save