From cafa84df5892ae5fd854b5f8e9e81e369832869b Mon Sep 17 00:00:00 2001 From: upmcplanetracker <219436948+upmcplanetracker@users.noreply.github.com> Date: Sat, 19 Sep 2026 07:29:50 -0400 Subject: [PATCH 1/3] Update Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6451f08..9713594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rand = "0.9.0" rbtag = "0.3.0" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" +socket2 = "0.5" strum = "0.27.1" strum_macros = "0.27.1" thiserror = "2.0.11" From 4a4812b90aee0fc1b2be9b2bac54ae6747090120 Mon Sep 17 00:00:00 2001 From: upmcplanetracker <219436948+upmcplanetracker@users.noreply.github.com> Date: Sat, 19 Sep 2026 07:32:54 -0400 Subject: [PATCH 2/3] Update Cargo.lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 2707d8b..8e88951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2696,6 +2696,7 @@ dependencies = [ "rbtag", "serde", "serde_json", + "socket2", "strum 0.27.1", "strum_macros 0.27.1", "thiserror 2.0.11", From bbdb8c7bf718aed794b8ca07ffebb69c79e14168 Mon Sep 17 00:00:00 2001 From: upmcplanetracker Date: Sat, 19 Sep 2026 07:42:44 -0400 Subject: [PATCH 3/3] Bind dual-stack ([::]) with IPv4 fallback --- src/http/mod.rs | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/http/mod.rs b/src/http/mod.rs index 86928c5..09dc09d 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,13 +1,42 @@ +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener}; + use actix_cors::Cors; use actix_web::{web, App, HttpServer}; -use log::info; +use log::{info, warn}; use services::{download::download, upload::upload, version::version, websocket::websocket}; +use socket2::{Domain, Protocol, Socket, Type}; use crate::http::services::keep::keep; mod response; mod services; +/// Binds a dual-stack listener on `[::]:port` that accepts both IPv6 and IPv4 +/// (via IPv4-mapped addresses). If IPv6 is unavailable (e.g. disabled in the +/// container), falls back to an IPv4-only listener on `0.0.0.0:port`. +fn bind_dual_stack(port: u16) -> std::io::Result { + let v6 = (|| -> std::io::Result { + let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?; + // Explicitly accept IPv4 too; the OS default differs (e.g. Windows/BSD). + socket.set_only_v6(false)?; + #[cfg(unix)] + socket.set_reuse_address(true)?; + socket.bind(&SocketAddr::from((Ipv6Addr::UNSPECIFIED, port)).into())?; + socket.listen(1024)?; + Ok(socket) + })(); + + let listener = match v6 { + Ok(socket) => TcpListener::from(socket), + Err(e) => { + warn!("IPv6 dual-stack bind failed ({e}); falling back to 0.0.0.0:{port}"); + TcpListener::bind((Ipv4Addr::UNSPECIFIED, port))? + } + }; + listener.set_nonblocking(true)?; + Ok(listener) +} + pub async fn start_http() -> anyhow::Result<()> { let server = HttpServer::new(|| { App::new() @@ -27,11 +56,11 @@ pub async fn start_http() -> anyhow::Result<()> { ) }); let port = std::env::var("PORT").unwrap_or_else(|_| "24153".to_string()); - if !port.chars().all(char::is_numeric) { - anyhow::bail!("PORT must be a number"); - } - let ip = format!("0.0.0.0:{}", port); - info!("http server listening on {}", ip); - server.bind(ip)?.run().await?; + let port: u16 = port + .parse() + .map_err(|_| anyhow::anyhow!("PORT must be a number between 0 and 65535"))?; + let listener = bind_dual_stack(port)?; + info!("http server listening on {}", listener.local_addr()?); + server.listen(listener)?.run().await?; Ok(()) }