diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | server/client-lib/src/network/tokio.rs | 19 | ||||
| -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 | ||||
| -rw-r--r-- | server/src/data/index.rs | 17 | ||||
| -rw-r--r-- | server/src/entity/tutorial.rs | 17 | ||||
| -rw-r--r-- | server/src/register.rs | 54 | 
10 files changed, 253 insertions, 36 deletions
| @@ -1115,6 +1115,7 @@ version = "0.1.0"  dependencies = [   "anyhow",   "env_logger", + "hurrycurry-client-lib",   "hurrycurry-protocol",   "log",   "markup", 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(())      } | 
