feat: /myip command + /v1/whoami endpoint
Server: - GET /v1/whoami returns client IP, IPv4/IPv6 classification - Detects proxy via X-Forwarded-For, X-Real-IP, Via headers - Shows proxy details when behind reverse proxy (Caddy etc) - ConnectInfo enabled for direct socket address Web client: - /myip, /whatsmyip, /ip — shows your IP + proxy info - Useful for testing IPv4/IPv6 connectivity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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::<std::net::SocketAddr>()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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<AppState> {
|
||||
Router::new().route("/health", get(health))
|
||||
Router::new()
|
||||
.route("/health", get(health))
|
||||
.route("/whoami", get(whoami))
|
||||
}
|
||||
|
||||
async fn health() -> Json<serde_json::Value> {
|
||||
Json(json!({ "status": "ok", "version": env!("CARGO_PKG_VERSION") }))
|
||||
}
|
||||
|
||||
async fn whoami(
|
||||
headers: HeaderMap,
|
||||
connect_info: Option<ConnectInfo<SocketAddr>>,
|
||||
) -> Json<serde_json::Value> {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user