aboutsummaryrefslogtreecommitdiff
path: root/server/registry/src/register.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/registry/src/register.rs')
-rw-r--r--server/registry/src/register.rs83
1 files changed, 83 insertions, 0 deletions
diff --git a/server/registry/src/register.rs b/server/registry/src/register.rs
new file mode 100644
index 00000000..173361ef
--- /dev/null
+++ b/server/registry/src/register.rs
@@ -0,0 +1,83 @@
+use crate::Registry;
+use log::{debug, info};
+use rocket::{http::hyper::Uri, post, serde::json::Json, State};
+use serde::Deserialize;
+use std::{net::IpAddr, str::FromStr, sync::Arc, time::Instant};
+use tokio::{net::lookup_host, sync::RwLock};
+
+#[derive(Debug, Deserialize)]
+pub(super) struct Submission {
+ secret: u128,
+ name: String,
+ players: usize,
+ last_game: i64,
+ version: (usize, usize),
+
+ uri: String,
+}
+
+#[post("/v1/register", data = "<submission>")]
+pub(super) async fn r_register<'a>(
+ client_addr: IpAddr,
+ registry: &State<Arc<RwLock<Registry>>>,
+ submission: Json<Submission>,
+) -> Result<&'static str, &'static str> {
+ debug!("submission {submission:?}");
+ let uri = Uri::from_str(&submission.uri).map_err(|_| "invalid uri")?;
+
+ let scheme = uri.scheme().ok_or("no scheme")?.as_str();
+ let secure = match scheme {
+ "ws" => false,
+ "wss" => true,
+ _ => return Err("invalid scheme"),
+ };
+ let host = uri.host().ok_or("no host")?;
+ let port = uri.port_u16().unwrap_or(if secure { 443 } else { 27032 });
+
+ let uri_q = match IpAddr::from_str(host) {
+ Ok(mut addr) => {
+ if addr.is_unspecified() {
+ addr = client_addr;
+ }
+ if addr.is_loopback() {
+ return Err("loopback address");
+ }
+ if addr.is_multicast() {
+ return Err("multicast address");
+ }
+ if client_addr == addr {
+ format!("{scheme}://{addr}:{port}",)
+ } else {
+ return Err("source address does not match uri");
+ }
+ }
+ Err(_) => {
+ if lookup_host(format!("{host}:0"))
+ .await
+ .map_err(|_| "dns lookup failed")?
+ .find(|a| a.ip() == client_addr)
+ .is_some()
+ {
+ format!("{scheme}://{host}:{port}")
+ } else {
+ return Err("host verification failed");
+ }
+ }
+ };
+
+ let mut g = registry.write().await;
+
+ if g.servers.len() > 1000 {
+ return Err("too many registered servers");
+ }
+
+ info!("submission approved for {uri_q:?}");
+ let entry = g.servers.entry(submission.secret).or_default();
+ entry.name = submission.name.clone();
+ entry.players_online = submission.players;
+ entry.last_game = submission.last_game;
+ entry.version = submission.version;
+ entry.address.insert(uri_q, Instant::now());
+
+ Ok("ok")
+}