aboutsummaryrefslogtreecommitdiff
path: root/server/registry/src/register.rs
blob: fb1c668a31038fd63a9b38417454322851e1b33e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
use crate::Registry;
use hurrycurry_protocol::registry::Submission;
use log::{debug, info};
use rocket::{http::hyper::Uri, post, serde::json::Json, State};
use std::{net::IpAddr, str::FromStr, sync::Arc, time::Instant};
use tokio::{net::lookup_host, sync::RwLock};

#[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")
}