diff --git a/warzone/crates/warzone-server/src/main.rs b/warzone/crates/warzone-server/src/main.rs index 467c671..9fcf81b 100644 --- a/warzone/crates/warzone-server/src/main.rs +++ b/warzone/crates/warzone-server/src/main.rs @@ -248,7 +248,7 @@ async fn main() -> anyhow::Result<()> { let listener = tokio::net::TcpListener::bind(&cli.bind).await?; tracing::info!("Listening on {}", cli.bind); - axum::serve(listener, app).await?; + axum::serve(listener, app.into_make_service_with_connect_info::()).await?; Ok(()) } diff --git a/warzone/crates/warzone-server/src/routes/health.rs b/warzone/crates/warzone-server/src/routes/health.rs index edbe6dc..1635f8b 100644 --- a/warzone/crates/warzone-server/src/routes/health.rs +++ b/warzone/crates/warzone-server/src/routes/health.rs @@ -1,12 +1,66 @@ -use axum::{routing::get, Json, Router}; +use axum::{extract::ConnectInfo, http::HeaderMap, routing::get, Json, Router}; use serde_json::json; +use std::net::SocketAddr; use crate::state::AppState; pub fn routes() -> Router { - Router::new().route("/health", get(health)) + Router::new() + .route("/health", get(health)) + .route("/whoami", get(whoami)) } async fn health() -> Json { Json(json!({ "status": "ok", "version": env!("CARGO_PKG_VERSION") })) } + +async fn whoami( + headers: HeaderMap, + connect_info: Option>, +) -> Json { + // Prefer X-Forwarded-For (set by Caddy/reverse proxy), then X-Real-Ip, then direct + let forwarded = headers + .get("x-forwarded-for") + .and_then(|v| v.to_str().ok()) + .map(|v| v.split(',').next().unwrap_or("").trim().to_string()); + + let real_ip = headers + .get("x-real-ip") + .and_then(|v| v.to_str().ok()) + .map(|s| s.to_string()); + + let direct = connect_info.map(|ci| ci.0.ip().to_string()); + + let ip = forwarded.clone() + .or(real_ip.clone()) + .or(direct.clone()) + .unwrap_or_else(|| "unknown".to_string()); + + // Classify as IPv4 or IPv6 + let is_v6 = ip.contains(':'); + + let via = headers.get("via").and_then(|v| v.to_str().ok()).map(|s| s.to_string()); + let proto = headers.get("x-forwarded-proto").and_then(|v| v.to_str().ok()).map(|s| s.to_string()); + let host = headers.get("x-forwarded-host").and_then(|v| v.to_str().ok()).map(|s| s.to_string()); + let behind_proxy = forwarded.is_some() || real_ip.is_some() || via.is_some(); + + let mut result = json!({ + "ip": ip, + "version": if is_v6 { "IPv6" } else { "IPv4" }, + "direct": direct, + "behind_proxy": behind_proxy, + }); + + if behind_proxy { + let proxy = json!({ + "x_forwarded_for": forwarded, + "x_real_ip": real_ip, + "x_forwarded_proto": proto, + "x_forwarded_host": host, + "via": via, + }); + result.as_object_mut().unwrap().insert("proxy".to_string(), proxy); + } + + Json(result) +} diff --git a/warzone/crates/warzone-server/src/routes/web.rs b/warzone/crates/warzone-server/src/routes/web.rs index 042637d..37c941a 100644 --- a/warzone/crates/warzone-server/src/routes/web.rs +++ b/warzone/crates/warzone-server/src/routes/web.rs @@ -1945,6 +1945,23 @@ async function doSend() { return; } if (text === '/debug') { DEBUG = !DEBUG; addSys('Debug logging: ' + (DEBUG ? 'ON (check browser console)' : 'OFF')); return; } + if (text === '/myip' || text === '/whatsmyip' || text === '/ip') { + try { + const resp = await fetch(SERVER + '/v1/whoami'); + const data = await resp.json(); + addSys('Your IP: ' + data.ip + ' (' + data.version + ')'); + if (data.behind_proxy && data.proxy) { + addSys(' Behind proxy: yes'); + if (data.proxy.x_forwarded_for) addSys(' X-Forwarded-For: ' + data.proxy.x_forwarded_for); + if (data.proxy.x_real_ip) addSys(' X-Real-IP: ' + data.proxy.x_real_ip); + if (data.proxy.x_forwarded_proto) addSys(' Proto: ' + data.proxy.x_forwarded_proto); + if (data.proxy.x_forwarded_host) addSys(' Host: ' + data.proxy.x_forwarded_host); + if (data.proxy.via) addSys(' Via: ' + data.proxy.via); + } + if (data.direct) addSys(' Direct connection: ' + data.direct); + } catch(e) { addSys('Error: ' + e.message); } + return; + } if (text === '/call') { startCall(); return; } if (text === '/hangup' || text === '/end') { hangupCall(); return; } if (text === '/accept') { acceptCall(); return; }