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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
/*
Hurry Curry! - a game about cooking
Copyright (C) 2025 Hurry Curry! Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::{conn_test::test_connect, Registry, MAX_SERVERS};
use anyhow::Result;
use hurrycurry_protocol::registry::Submission;
use log::{debug, info};
use rocket::{http::hyper::Uri, post, serde::json::Json, State};
use std::{
net::{IpAddr, SocketAddr},
str::FromStr,
sync::Arc,
time::Instant,
};
use tokio::{net::lookup_host, sync::RwLock};
#[post("/v1/register", data = "<submission>")]
pub(super) async fn r_register(
client_addr: IpAddr,
registry: &State<Arc<RwLock<Registry>>>,
submission: Json<Submission>,
) -> Result<&'static str, &'static str> {
match register_inner(client_addr, registry, &submission).await {
Ok(()) => {
info!("ok");
Ok("ok")
}
Err(e) => {
info!("err: {e}");
Err(e)
}
}
}
async fn register_inner(
client_addr: IpAddr,
registry: &Arc<RwLock<Registry>>,
submission: &Submission,
) -> Result<(), &'static str> {
debug!("submission {submission:?} from {client_addr}");
let client_addr = client_addr.to_canonical();
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 (saddr, 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");
}
info!("{client_addr} != {addr}");
let addr = addr.to_canonical();
if client_addr == addr {
let saddr = SocketAddr::new(addr, port);
(saddr, format!("{scheme}://{saddr}"))
} else {
return Err("source address does not match uri");
}
}
Err(_) => {
if let Some(addr) = lookup_host(format!("{host}:0"))
.await
.map_err(|_| "dns lookup failed")?
.find(|a| a.ip().to_canonical() == client_addr)
{
(
SocketAddr::new(addr.ip().to_canonical(), port),
format!("{scheme}://{host}:{port}"),
)
} else {
return Err("host verification failed");
}
}
};
test_connect(saddr, &uri_q).await?;
let mut g = registry.write().await;
if g.servers.len() > MAX_SERVERS {
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(())
}
|