diff options
author | metamuffin <metamuffin@disroot.org> | 2024-09-21 12:48:36 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-09-21 12:48:36 +0200 |
commit | 41c95fc5b6b1c8bc4b944d889414a4197a23837b (patch) | |
tree | 86519d51df113263e0c1e6d92b96b35c72cf37d9 /server/registry | |
parent | 526bde82158fc58f59a8147566ef0ef01845ac49 (diff) | |
download | hurrycurry-41c95fc5b6b1c8bc4b944d889414a4197a23837b.tar hurrycurry-41c95fc5b6b1c8bc4b944d889414a4197a23837b.tar.bz2 hurrycurry-41c95fc5b6b1c8bc4b944d889414a4197a23837b.tar.zst |
reg: connection test
Diffstat (limited to 'server/registry')
-rw-r--r-- | server/registry/Cargo.toml | 1 | ||||
-rw-r--r-- | server/registry/src/conn_test.rs | 78 | ||||
-rw-r--r-- | server/registry/src/list.rs | 17 | ||||
-rw-r--r-- | server/registry/src/main.rs | 21 | ||||
-rw-r--r-- | server/registry/src/register.rs | 64 |
5 files changed, 171 insertions, 10 deletions
diff --git a/server/registry/Cargo.toml b/server/registry/Cargo.toml index 2db74981..8acd3c4c 100644 --- a/server/registry/Cargo.toml +++ b/server/registry/Cargo.toml @@ -14,3 +14,4 @@ markup = "0.15.0" serde = { version = "1.0.210", features = ["derive"] } hurrycurry-protocol = { path = "../protocol" } +hurrycurry-client-lib = { path = "../client-lib" } diff --git a/server/registry/src/conn_test.rs b/server/registry/src/conn_test.rs new file mode 100644 index 00000000..2fda288c --- /dev/null +++ b/server/registry/src/conn_test.rs @@ -0,0 +1,78 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + 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::MAX_ADDR_CACHE; +use anyhow::{bail, Result}; +use hurrycurry_client_lib::network::tokio::Network; +use hurrycurry_protocol::PacketC; +use log::info; +use std::{ + collections::BTreeMap, + net::SocketAddr, + time::{Duration, Instant}, +}; +use tokio::{net::TcpStream, sync::RwLock, time::timeout}; + +#[derive(Debug, Clone, Copy)] +struct ConnectTest { + verified_at: Instant, + version: (u32, u32), +} + +static CONNECT_OK: RwLock<BTreeMap<SocketAddr, ConnectTest>> = RwLock::const_new(BTreeMap::new()); + +pub(crate) async fn test_connect(addr: SocketAddr, uri: &str) -> Result<(u32, u32), &'static str> { + let r = CONNECT_OK.read().await.get(&addr).copied(); + if let Some(r) = r { + Ok(r.version) + } else { + // TODO locks to prevent parallel tests for same addr and dos attempts + let res = timeout(Duration::from_secs(10), test_connect_inner(addr, uri)) + .await + .map_err(|_| "connect timeout")?; + info!("connect result: {res:?}"); + let version = res.map_err(|_| "server unreachable")?; + { + let mut g = CONNECT_OK.write().await; + g.insert( + addr, + ConnectTest { + verified_at: Instant::now(), + version, + }, + ); + while g.len() > MAX_ADDR_CACHE { + // TODO perf maybe later + if let Some(key) = g.iter().min_by_key(|(_, v)| v.verified_at).map(|(k, _)| *k) { + g.remove(&key); + } + } + } + info!("cache updated"); + Ok(version) + } +} +async fn test_connect_inner(addr: SocketAddr, uri: &str) -> Result<(u32, u32)> { + info!("test connect {addr} {uri:?}"); + let stream = TcpStream::connect(addr).await?; + let net = Network::connect_raw(stream, uri).await?; + let packet = net.receive().await?; + match packet { + Some(PacketC::Version { minor, major, .. }) => return Ok((major, minor)), + _ => bail!("bad initial packet"), + } +} diff --git a/server/registry/src/list.rs b/server/registry/src/list.rs index 67f2ec2a..1c2cd4a3 100644 --- a/server/registry/src/list.rs +++ b/server/registry/src/list.rs @@ -1,3 +1,20 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + 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::Registry; use anyhow::Result; use hurrycurry_protocol::registry::Entry; diff --git a/server/registry/src/main.rs b/server/registry/src/main.rs index c24aecbe..670ac3a6 100644 --- a/server/registry/src/main.rs +++ b/server/registry/src/main.rs @@ -1,3 +1,21 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + 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/>. + +*/ +pub mod conn_test; pub mod list; pub mod register; @@ -17,6 +35,9 @@ use std::{ }; use tokio::{sync::RwLock, time::interval}; +const MAX_ADDR_CACHE: usize = 4096; +const MAX_SERVERS: usize = 128; + fn main() { env_logger::init_from_env("LOG"); let registry = Arc::new(RwLock::new(Registry::default())); diff --git a/server/registry/src/register.rs b/server/registry/src/register.rs index fb1c668a..603f5a4a 100644 --- a/server/registry/src/register.rs +++ b/server/registry/src/register.rs @@ -1,8 +1,31 @@ -use crate::Registry; +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + 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, str::FromStr, sync::Arc, time::Instant}; +use std::{ + net::{IpAddr, SocketAddr}, + str::FromStr, + sync::Arc, + time::Instant, +}; use tokio::{net::lookup_host, sync::RwLock}; #[post("/v1/register", data = "<submission>")] @@ -11,6 +34,23 @@ pub(super) async fn r_register<'a>( 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:?}"); let uri = Uri::from_str(&submission.uri).map_err(|_| "invalid uri")?; @@ -23,7 +63,7 @@ pub(super) async fn r_register<'a>( 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) { + let (saddr, uri_q) = match IpAddr::from_str(host) { Ok(mut addr) => { if addr.is_unspecified() { addr = client_addr; @@ -35,31 +75,35 @@ pub(super) async fn r_register<'a>( return Err("multicast address"); } if client_addr == addr { - format!("{scheme}://{addr}:{port}",) + let saddr = SocketAddr::new(addr, port); + (saddr, format!("{scheme}://{saddr}")) } else { return Err("source address does not match uri"); } } Err(_) => { - if lookup_host(format!("{host}:0")) + if let Some(addr) = lookup_host(format!("{host}:0")) .await .map_err(|_| "dns lookup failed")? .find(|a| a.ip() == client_addr) - .is_some() { - format!("{scheme}://{host}:{port}") + ( + SocketAddr::new(addr.ip(), 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() > 1000 { + 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(); @@ -68,5 +112,5 @@ pub(super) async fn r_register<'a>( entry.version = submission.version; entry.address.insert(uri_q, Instant::now()); - Ok("ok") + Ok(()) } |