From 3b965f1f3f938bc4a575ea4f1e6dd45ffecf7626 Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 26 Jun 2026 09:20:04 -0700 Subject: [PATCH] feat: add setcap step to Setup tab for port 443 binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Step 2 in the Setup tab: grants cap_net_bind_service to the bridge binary via pkexec/sudo so it can bind port 443 directly. On success, automatically updates bridge_listen_addr to 0.0.0.0:443 — no iptables rules required. Step re-detects cap state on each frame so it stays accurate after a rebuild clears the capability. Co-Authored-By: Claude Sonnet 4.6 --- src/app.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/setup.rs | 21 +++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index db836fa..f98f3a8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -34,6 +34,7 @@ pub struct LauncherApp { // Setup state hook_deployed: bool, + bridge_has_cap: bool, cert_path: Option, setup_message: Option<(bool, String)>, // (success, text) } @@ -56,6 +57,8 @@ impl LauncherApp { let cert_path = setup::find_bridge_cert(&config.bridge_captures_dir); let hook_deployed = setup::hook_dll_deployed(std::path::Path::new(&config.fifa_game_dir)); + let bridge_has_cap = + setup::bridge_has_cap443(std::path::Path::new(&config.bridge_binary)); Self { config, @@ -68,6 +71,7 @@ impl LauncherApp { log_tab: 0, log_follow: true, hook_deployed, + bridge_has_cap, cert_path, setup_message: None, } @@ -379,10 +383,70 @@ impl LauncherApp { ui.add_space(12.0); + // ── Port 443 capability ─────────────────────────────────────────────── + ui.group(|ui| { + ui.set_min_width(ui.available_width()); + ui.strong("Step 2 — Grant port 443 capability"); + ui.add_space(4.0); + ui.label("Allows the bridge to bind port 443 directly — the same port FIFA 23 \ + uses for HTTPS — without running as root. No iptables rules needed."); + ui.add_space(6.0); + + // Clone to avoid holding a borrow on config while we mutate it below. + let binary_path = std::path::PathBuf::from(&self.config.bridge_binary); + self.bridge_has_cap = setup::bridge_has_cap443(&binary_path); + + ui.horizontal(|ui| { + if self.bridge_has_cap { + ui.colored_label(Color32::from_rgb(80, 200, 120), "✔ cap_net_bind_service granted"); + } else { + ui.colored_label(Color32::from_rgb(220, 60, 60), "✘ Not set"); + ui.add_space(8.0); + let binary_exists = binary_path.exists(); + if ui + .add_enabled(binary_exists, egui::Button::new("Grant (requires sudo)")) + .clicked() + { + match setup::setcap_bridge_443(&binary_path) { + Ok(()) => { + self.bridge_has_cap = true; + // Switch listen addr to 443 automatically + self.config.bridge_listen_addr = "0.0.0.0:443".into(); + self.config.save(); + self.setup_message = Some(( + true, + "cap_net_bind_service granted. Bridge listen addr set to 0.0.0.0:443.".into(), + )); + } + Err(e) => self.setup_message = Some((false, format!("setcap failed: {e}"))), + } + } + if !binary_exists { + ui.add_space(4.0); + ui.colored_label( + Color32::from_rgb(220, 150, 0), + "⚠ Bridge binary not found — build it first.", + ); + } + } + }); + + ui.add_space(4.0); + ui.label( + egui::RichText::new( + "Re-run this step any time the bridge binary is rebuilt (setcap is cleared on recompile).", + ) + .weak() + .small(), + ); + }); + + ui.add_space(12.0); + // ── Cert install ────────────────────────────────────────────────────── ui.group(|ui| { ui.set_min_width(ui.available_width()); - ui.strong("Step 2 — Install TLS certificate"); + ui.strong("Step 3 — Install TLS certificate"); ui.add_space(4.0); ui.label("Installs the bridge's self-signed cert into the Wine/Proton cert store \ so the game accepts HTTPS connections to the bridge."); diff --git a/src/setup.rs b/src/setup.rs index e9f94dc..86cb64d 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,5 +1,26 @@ use std::{path::{Path, PathBuf}, process::Command}; +// ── Port 443 capability ─────────────────────────────────────────────────────── + +/// Check whether the bridge binary already has cap_net_bind_service set. +pub fn bridge_has_cap443(binary: &Path) -> bool { + std::process::Command::new("getcap") + .arg(binary) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).contains("cap_net_bind_service")) + .unwrap_or(false) +} + +/// Grant cap_net_bind_service to the bridge binary so it can bind port 443 +/// without running as root. Uses pkexec (or sudo as fallback). +pub fn setcap_bridge_443(binary: &Path) -> anyhow::Result<()> { + let script = format!( + "setcap cap_net_bind_service=+ep '{}'", + binary.to_string_lossy() + ); + run_elevated(&script) +} + // ── Cert installation ───────────────────────────────────────────────────────── /// Find the bridge cert in the given captures dir.