Add --trust-proxy flag for extracting client IPs behind reverse proxies
When running behind a reverse proxy (nginx, caddy, traefik, etc.), the server sees the proxy's IP instead of the real client IP. The new --trust-proxy flag makes the server read X-Forwarded-For and X-Real-IP headers to extract the actual client IP. Falls back to the socket address if no proxy header is present. https://claude.ai/code/session_01Mp2BozcMQX2kWm4ZP9mxJw
This commit is contained in:
parent
0df86e79ff
commit
91ce009271
1 changed files with 52 additions and 5 deletions
|
|
@ -70,6 +70,15 @@ struct Args {
|
|||
/// connect via ws:// instead.
|
||||
#[arg(long, default_value_t = 8081, value_name = "PORT")]
|
||||
http_port: u16,
|
||||
|
||||
/// Trust proxy headers (X-Forwarded-For, X-Real-IP) for client IPs
|
||||
///
|
||||
/// Enable this when running behind a reverse proxy (e.g. nginx, caddy,
|
||||
/// traefik). The server will use the IP from proxy headers instead of
|
||||
/// the direct connection address. Do NOT enable this without a trusted
|
||||
/// proxy, as clients can spoof these headers.
|
||||
#[arg(long)]
|
||||
trust_proxy: bool,
|
||||
}
|
||||
|
||||
/// Messages sent between peers via the signaling server
|
||||
|
|
@ -155,12 +164,14 @@ struct PeerState {
|
|||
/// Application state shared across all connections
|
||||
struct AppState {
|
||||
peers: DashMap<String, PeerState>,
|
||||
trust_proxy: bool,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn new() -> Self {
|
||||
fn new(trust_proxy: bool) -> Self {
|
||||
Self {
|
||||
peers: DashMap::new(),
|
||||
trust_proxy,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +213,7 @@ async fn main() {
|
|||
)
|
||||
.init();
|
||||
|
||||
let state = Arc::new(AppState::new());
|
||||
let state = Arc::new(AppState::new(args.trust_proxy));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(serve_index))
|
||||
|
|
@ -244,6 +255,9 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if args.trust_proxy {
|
||||
println!(" \x1b[1;36mProxy mode:\x1b[0m trusting X-Forwarded-For / X-Real-IP headers");
|
||||
}
|
||||
println!();
|
||||
println!(" \x1b[90mOpen this URL in browsers on your local network.\x1b[0m");
|
||||
println!(" \x1b[90mPress Ctrl+C to stop the server.\x1b[0m");
|
||||
|
|
@ -290,14 +304,47 @@ async fn serve_index() -> impl IntoResponse {
|
|||
async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
headers: axum::http::HeaderMap,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(move |socket| handle_socket(socket, state, addr))
|
||||
let peer_ip = if state.trust_proxy {
|
||||
extract_proxy_ip(&headers).unwrap_or_else(|| addr.ip().to_string())
|
||||
} else {
|
||||
addr.ip().to_string()
|
||||
};
|
||||
ws.on_upgrade(move |socket| handle_socket(socket, state, peer_ip))
|
||||
}
|
||||
|
||||
async fn handle_socket(socket: WebSocket, state: Arc<AppState>, addr: SocketAddr) {
|
||||
/// Extract the client IP from proxy headers.
|
||||
/// Checks X-Forwarded-For first (uses the leftmost/first IP), then X-Real-IP.
|
||||
fn extract_proxy_ip(headers: &axum::http::HeaderMap) -> Option<String> {
|
||||
// X-Forwarded-For: client, proxy1, proxy2
|
||||
if let Some(forwarded_for) = headers.get("x-forwarded-for") {
|
||||
if let Ok(value) = forwarded_for.to_str() {
|
||||
if let Some(first_ip) = value.split(',').next() {
|
||||
let trimmed = first_ip.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// X-Real-IP: client
|
||||
if let Some(real_ip) = headers.get("x-real-ip") {
|
||||
if let Ok(value) = real_ip.to_str() {
|
||||
let trimmed = value.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn handle_socket(socket: WebSocket, state: Arc<AppState>, peer_ip: String) {
|
||||
let peer_id = Uuid::new_v4().to_string();
|
||||
let peer_ip = addr.ip().to_string();
|
||||
let (tx, _) = broadcast::channel::<SignalMessage>(64);
|
||||
|
||||
// Add peer to state with default name
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue