diff options
author | metamuffin <metamuffin@disroot.org> | 2024-09-21 15:31:18 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-09-21 15:31:18 +0200 |
commit | 34190a70b1efa0972ef58b88d356f985c46b89ae (patch) | |
tree | d8cf5b98a4d0aed34bff048fb1e15a87f106f6db /server/src/network | |
parent | 41c95fc5b6b1c8bc4b944d889414a4197a23837b (diff) | |
download | hurrycurry-34190a70b1efa0972ef58b88d356f985c46b89ae.tar hurrycurry-34190a70b1efa0972ef58b88d356f985c46b89ae.tar.bz2 hurrycurry-34190a70b1efa0972ef58b88d356f985c46b89ae.tar.zst |
server: mdns and upnp
Diffstat (limited to 'server/src/network')
-rw-r--r-- | server/src/network/mdns.rs | 61 | ||||
-rw-r--r-- | server/src/network/mod.rs | 20 | ||||
-rw-r--r-- | server/src/network/register.rs | 132 | ||||
-rw-r--r-- | server/src/network/upnp.rs | 72 |
4 files changed, 285 insertions, 0 deletions
diff --git a/server/src/network/mdns.rs b/server/src/network/mdns.rs new file mode 100644 index 00000000..870ae7ec --- /dev/null +++ b/server/src/network/mdns.rs @@ -0,0 +1,61 @@ +use crate::server::Server; +use anyhow::Result; +use get_if_addrs::get_if_addrs; +use hurrycurry_protocol::VERSION; +use log::{info, warn}; +use mdns_sd::{ServiceDaemon, ServiceInfo}; +use rand::random; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use tokio::{sync::RwLock, time::interval}; + +pub async fn mdns_loop(name: String, port: u16, state: Arc<RwLock<Server>>) { + let d = match ServiceDaemon::new() { + Ok(d) => d, + Err(e) => { + warn!("mDNS daemon failed to start: {e}"); + return; + } + }; + let mut interval = interval(Duration::from_secs(60)); + let hostname = format!("hks-{}.local.", random::<u64>()); + loop { + interval.tick().await; + if let Err(e) = update_service(&d, &state, &name, &hostname, port).await { + warn!("mDNS service update failed: {e}"); + } + info!("updated mDNS service record"); + } +} + +async fn update_service( + d: &ServiceDaemon, + state: &Arc<RwLock<Server>>, + name: &str, + hostname: &str, + port: u16, +) -> Result<()> { + let addrs = get_if_addrs()? + .into_iter() + .map(|e| e.addr.ip()) + .filter(|a| !a.is_loopback()) + .collect::<Vec<_>>(); + + let players = state.read().await.count_chefs(); + + d.register(ServiceInfo::new( + "_hurrycurry._tcp.local.", + name, + hostname, + &*addrs, + port, + HashMap::from_iter([ + ("players".to_string(), players.to_string()), + ("version".to_string(), env!("CARGO_PKG_VERSION").to_string()), + ( + "protocol".to_string(), + format!("{}.{}", VERSION.0, VERSION.1), + ), + ]), + )?)?; + Ok(()) +} diff --git a/server/src/network/mod.rs b/server/src/network/mod.rs new file mode 100644 index 00000000..b7ffc15e --- /dev/null +++ b/server/src/network/mod.rs @@ -0,0 +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/>. + +*/ +pub mod register; +pub mod upnp; +pub mod mdns; diff --git a/server/src/network/register.rs b/server/src/network/register.rs new file mode 100644 index 00000000..b26768a2 --- /dev/null +++ b/server/src/network/register.rs @@ -0,0 +1,132 @@ +/* + 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, Result}; +use hurrycurry_protocol::{registry::Submission, VERSION}; +use log::{info, warn}; +use rand::random; +use reqwest::{header::USER_AGENT, Client, Url}; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + str::FromStr, + sync::Arc, + time::Duration, +}; +use tokio::{sync::RwLock, time::interval}; + +const REGISTRY_URI: &'static str = "https://hurrycurry-registry.metamuffin.org"; + +pub struct Register { + name: String, + port: u16, + register_uri: Option<String>, + state: Arc<RwLock<Server>>, + inet_client: Client, + ip4_client: Client, + ip6_client: Client, + secret: u128, + players: usize, +} + +impl Register { + pub fn new( + name: String, + port: u16, + register_uri: Option<String>, + state: Arc<RwLock<Server>>, + ) -> Self { + Self { + name, + register_uri, + players: 0, + port, + secret: random(), + state, + inet_client: Client::new(), + ip4_client: Client::builder() + .local_address(IpAddr::V4(Ipv4Addr::UNSPECIFIED)) + .build() + .unwrap(), + ip6_client: Client::builder() + .local_address(IpAddr::V6(Ipv6Addr::UNSPECIFIED)) + .build() + .unwrap(), + } + } + pub async fn register_loop(mut self) { + let mut interval = interval(Duration::from_secs(60)); + loop { + interval.tick().await; + self.players = self.state.read().await.count_chefs(); + if let Err(e) = self.register().await { + warn!("register error: {e}") + } + } + } + + 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 (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), + ) + ); + info!("v4: {v4:?}"); + info!("v6: {v6:?}"); + } + Ok(()) + } + // TODO ip v6 + pub async fn register_with(&self, label: &str, client: &Client, uri: String) -> Result<()> { + let res = client + .post(Url::from_str(&format!("{REGISTRY_URI}/v1/register")).unwrap()) + .header( + USER_AGENT, + format!("hurrycurry-server {}", env!("CARGO_PKG_VERSION")), + ) + .json(&Submission { + last_game: 0, + name: self.name.clone(), + uri, + players: self.players, + secret: self.secret, + version: VERSION, + }) + .send() + .await?; + + let r = res.text().await?; + if r == "ok" { + info!("register ok ({label})"); + Ok(()) + } else { + bail!("{r}"); + } + } +} diff --git a/server/src/network/upnp.rs b/server/src/network/upnp.rs new file mode 100644 index 00000000..a16e9684 --- /dev/null +++ b/server/src/network/upnp.rs @@ -0,0 +1,72 @@ +/* + 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 anyhow::{bail, Result}; +use get_if_addrs::get_if_addrs; +use igd::{ + aio::{search_gateway, Gateway}, + PortMappingProtocol, SearchOptions, +}; +use log::{error, info}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddrV4}, + time::Duration, +}; +use tokio::time::interval; + +pub async fn upnp_loop(port: u16) { + // TODO multiple gateways + let (gateway, local_addr) = match upnp_setup().await { + Ok(g) => g, + Err(e) => { + error!("UPnP gateway not found: {e}"); + return; + } + }; + let mut interval = interval(Duration::from_secs(120)); + loop { + interval.tick().await; + match gateway + .add_port( + PortMappingProtocol::TCP, + 27032, + SocketAddrV4::new(local_addr, port), + 800, + "Hurry Curry! Game Server", + ) + .await + { + Ok(()) => info!("port mapped successfully"), + Err(e) => error!("upnp failed: {e}"), + } + } +} + +async fn upnp_setup() -> Result<(Gateway, Ipv4Addr)> { + let gateway = search_gateway(SearchOptions::default()).await?; + info!("IGD address is {}", gateway.addr); + for i in get_if_addrs()? { + let a = i.addr.ip(); + if !a.is_loopback() { + if let IpAddr::V4(a) = a { + info!("local v4 address is {a}"); + return Ok((gateway, a)); + } + } + } + bail!("no good local address found") +} |