aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/client-lib/src/network/tokio.rs19
-rw-r--r--server/registry/Cargo.toml1
-rw-r--r--server/registry/src/conn_test.rs78
-rw-r--r--server/registry/src/list.rs17
-rw-r--r--server/registry/src/main.rs21
-rw-r--r--server/registry/src/register.rs64
-rw-r--r--server/src/data/index.rs17
-rw-r--r--server/src/entity/tutorial.rs17
-rw-r--r--server/src/register.rs54
9 files changed, 252 insertions, 36 deletions
diff --git a/server/client-lib/src/network/tokio.rs b/server/client-lib/src/network/tokio.rs
index 60cafd95..3f68e091 100644
--- a/server/client-lib/src/network/tokio.rs
+++ b/server/client-lib/src/network/tokio.rs
@@ -15,7 +15,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use futures_util::{
stream::{SplitSink, SplitStream, StreamExt},
SinkExt, TryStreamExt,
@@ -39,8 +39,8 @@ pub struct Network {
}
impl Network {
- pub async fn connect(addr: &str) -> Result<Self> {
- let (parts, _) = addr.into_client_request().unwrap().into_parts();
+ pub async fn connect(uri: &str) -> Result<Self> {
+ let (parts, _) = uri.into_client_request().unwrap().into_parts();
let mut builder = Request::builder()
.uri(parts.uri.clone().clone())
.method(parts.method.clone())
@@ -64,7 +64,18 @@ impl Network {
info!("Connecting: host={host:?} port={port}");
let stream = TcpStream::connect((host, port)).await?;
- stream.set_nodelay(true).unwrap();
+ Self::connect_raw(stream, uri).await
+ }
+ pub async fn connect_raw(stream: TcpStream, uri: &str) -> Result<Self> {
+ let (parts, _) = uri.into_client_request()?.into_parts();
+ let mut builder = Request::builder()
+ .uri(parts.uri.clone().clone())
+ .method(parts.method.clone())
+ .version(parts.version);
+ *builder.headers_mut().ok_or(anyhow!("???"))? = parts.headers.clone();
+ let request = builder.body(())?;
+
+ stream.set_nodelay(true)?;
let (sock, _) = client_async_tls_with_config(request, stream, None, None).await?;
info!("Handshake complete.");
let (sock_send, sock_recv) = sock.split();
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(())
}
diff --git a/server/src/data/index.rs b/server/src/data/index.rs
index e056cfc0..650a70d6 100644
--- a/server/src/data/index.rs
+++ b/server/src/data/index.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 hurrycurry_protocol::{Gamedata, ItemIndex, Recipe, RecipeIndex};
use std::collections::HashMap;
diff --git a/server/src/entity/tutorial.rs b/server/src/entity/tutorial.rs
index 66533119..b1503004 100644
--- a/server/src/entity/tutorial.rs
+++ b/server/src/entity/tutorial.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 super::{Entity, EntityContext};
use crate::{trm, TrError};
use anyhow::Result;
diff --git a/server/src/register.rs b/server/src/register.rs
index 2aaec770..b26768a2 100644
--- a/server/src/register.rs
+++ b/server/src/register.rs
@@ -1,5 +1,22 @@
+/*
+ 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::server::Server;
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
use hurrycurry_protocol::{registry::Submission, VERSION};
use log::{info, warn};
use rand::random;
@@ -63,32 +80,25 @@ impl Register {
}
pub async fn register(&self) -> Result<()> {
+ info!("register update");
if let Some(uri) = &self.register_uri {
self.register_with("uri", &self.inet_client, uri.to_owned())
.await?;
} else {
- let x = tokio::join!(
- async {
- self.register_with(
- "ip4",
- &self.ip4_client,
- format!("ws://0.0.0.0:{}", self.port),
- )
- .await
- .context("ipv4")
- },
- async {
- self.register_with(
- "ip6",
- &self.ip6_client,
- format!("ws://0.0.0.0:{}", self.port),
- )
- .await
- .context("ipv6")
- }
+ let (v4, v6) = tokio::join!(
+ self.register_with(
+ "ip4",
+ &self.ip4_client,
+ format!("ws://0.0.0.0:{}", self.port),
+ ),
+ self.register_with(
+ "ip6",
+ &self.ip6_client,
+ format!("ws://0.0.0.0:{}", self.port),
+ )
);
- x.0?;
- x.1?;
+ info!("v4: {v4:?}");
+ info!("v6: {v6:?}");
}
Ok(())
}