ruvnet/RuView · error

cannot bind UDP socket on {udp_addr}: {e}

Error message

cannot bind UDP socket on {udp_addr}: {e}

What it means

tokio UdpSocket::bind failed for the CSI ingest address {udp_bind}:{udp_port} at calibrate-serve startup. The io::Error is typically AddrInUse (another process already bound that UDP port, e.g. a previous calibrate-serve, enroll, or room-watch instance), permission denied for privileged ports below 1024, or an invalid/unassignable --udp-bind address.

Source

Thrown at v2/crates/wifi-densepose-cli/src/calibrate_api.rs:320

        .route("/api/v1/calibration/baselines", get(baselines))
        .route("/api/v1/room/state", get(room_state))
        .route("/api/v1/room/train", post(train_room))
        .route("/api/v1/enroll/anchor", post(enroll_anchor))
        .route("/api/v1/enroll/geometry", post(enroll_geometry))
        .route("/api/v1/enroll/status", get(enroll_status))
        .layer(CorsLayer::permissive())
        .with_state(state)
}

/// Run the calibration HTTP API server (blocks until Ctrl-C).
pub async fn execute(args: CalibrateServeArgs) -> Result<()> {
    std::fs::create_dir_all(&args.output_dir)
        .map_err(|e| anyhow::anyhow!("cannot create output dir {}: {e}", args.output_dir))?;

    let udp_addr = format!("{}:{}", args.udp_bind, args.udp_port);
    let socket = UdpSocket::bind(&udp_addr)
        .await
        .map_err(|e| anyhow::anyhow!("cannot bind UDP socket on {udp_addr}: {e}"))?;
    eprintln!("[calibrate-serve] CSI ingest on udp://{udp_addr}");

    let status = Arc::new(RwLock::new(SharedStatus {
        udp_port: args.udp_port,
        default_tier: args.tier.clone(),
        output_dir: args.output_dir.clone(),
        ..Default::default()
    }));

    let (cmd_tx, cmd_rx) = mpsc::channel::<CalCommand>(8);
    let window = Arc::new(RwLock::new(VecDeque::<f32>::with_capacity(LIVE_WINDOW)));
    let enroll = Arc::new(RwLock::new(HashMap::<String, RoomEnroll>::new()));

    // Background ingest task owns the socket + recorder.
    {
        let status = status.clone();
        let default_tier = args.tier.clone();
        let output_dir = args.output_dir.clone();

View on GitHub (pinned to 4685618388)

Solutions

  1. Free the port: stop the other process (ss -ulpn / lsof -i udp/<port>) or pick a different --udp-port
  2. Use a UDP port of 1024 or higher, or grant the capability when a low port is required
  3. Pass a numeric, bindable address in --udp-bind (127.0.0.1, 0.0.0.0, or a NIC IP)
  4. After changing the port, align the ESP32 firmware's CSI destination port with it

Example fix

// before
$ ruview calibrate-serve --udp-port 9999   # cannot bind: AddrInUse (old instance)

// after
$ ss -ulpn | grep 9999
$ kill <old-pid>
$ ruview calibrate-serve --udp-port 9999
Defensive patterns

Strategy: validation

Validate before calling

fn udp_port_free(bind: &str, port: u16) -> bool {
    std::net::UdpSocket::bind((bind, port)).is_ok()
}

Try / catch

match UdpSocket::bind(&udp_addr).await {
    Ok(socket) => socket,
    Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
        return Err(anyhow::anyhow!("udp://{udp_addr} already in use — stop the other CSI tool or change --udp-port"));
    }
    Err(e) => return Err(anyhow::anyhow!("cannot bind UDP socket on {udp_addr}: {e}")),
}

Prevention

When it happens

Trigger: Starting calibrate-serve while another calibrate-serve/enroll/room-watch still holds the same UDP port; --udp-port below 1024 as non-root; passing a --udp-bind literal that cannot be parsed or is not assigned to the host.

Common situations: A previous server instance was never stopped (orphaned process, missing systemd stop); two CSI tools configured to the same default port; containers without CAP_NET_BIND_SERVICE trying low ports.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/69be4a2b710eb0a8. Report an issue: GitHub.