Compare commits

...

3 commits

Author SHA1 Message Date
82a8418b82 Package lanshare
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-02-09 14:54:03 +01:00
dabe5ef0fe
Merge pull request #6 from TheNeikos/claude/add-proxy-ip-support-lLTZv
Add support for trusting proxy headers for client IP detection
2026-02-09 14:52:42 +01:00
Claude
91ce009271
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
2026-02-09 13:52:05 +00:00
2 changed files with 92 additions and 27 deletions

View file

@ -14,7 +14,24 @@
] (system: f (import inputs.nixpkgs { inherit system; })); ] (system: f (import inputs.nixpkgs { inherit system; }));
in in
{ {
packages = forAllSystems (pkgs: { packages = forAllSystems (
pkgs:
let
lanshare = pkgs.rustPlatform.buildRustPackage (finalAttrs: {
pname = "lanshare";
version = "0.1.0";
src = ./lanshare;
cargoHash = "sha256-rUTARzVTbi0/KNFUDRynOTioIZIhiZf4suqb9UqtvV4=";
meta = {
description = "A local screenshare application";
mainProgram = "lanshare";
};
});
in
{
inherit lanshare;
gitlab-job-status = pkgs.writeShellApplication { gitlab-job-status = pkgs.writeShellApplication {
name = "gitlab-job-status"; name = "gitlab-job-status";
runtimeInputs = [ runtimeInputs = [
@ -38,6 +55,7 @@
text = builtins.readFile ./migrate-workspace-deps; text = builtins.readFile ./migrate-workspace-deps;
}; };
}); }
);
}; };
} }

View file

@ -70,6 +70,15 @@ struct Args {
/// connect via ws:// instead. /// connect via ws:// instead.
#[arg(long, default_value_t = 8081, value_name = "PORT")] #[arg(long, default_value_t = 8081, value_name = "PORT")]
http_port: u16, 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 /// Messages sent between peers via the signaling server
@ -155,12 +164,14 @@ struct PeerState {
/// Application state shared across all connections /// Application state shared across all connections
struct AppState { struct AppState {
peers: DashMap<String, PeerState>, peers: DashMap<String, PeerState>,
trust_proxy: bool,
} }
impl AppState { impl AppState {
fn new() -> Self { fn new(trust_proxy: bool) -> Self {
Self { Self {
peers: DashMap::new(), peers: DashMap::new(),
trust_proxy,
} }
} }
@ -202,7 +213,7 @@ async fn main() {
) )
.init(); .init();
let state = Arc::new(AppState::new()); let state = Arc::new(AppState::new(args.trust_proxy));
let app = Router::new() let app = Router::new()
.route("/", get(serve_index)) .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!();
println!(" \x1b[90mOpen this URL in browsers on your local network.\x1b[0m"); println!(" \x1b[90mOpen this URL in browsers on your local network.\x1b[0m");
println!(" \x1b[90mPress Ctrl+C to stop the server.\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( async fn ws_handler(
ws: WebSocketUpgrade, ws: WebSocketUpgrade,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
headers: axum::http::HeaderMap,
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
) -> impl IntoResponse { ) -> 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_id = Uuid::new_v4().to_string();
let peer_ip = addr.ip().to_string();
let (tx, _) = broadcast::channel::<SignalMessage>(64); let (tx, _) = broadcast::channel::<SignalMessage>(64);
// Add peer to state with default name // Add peer to state with default name