GyulyVGC/sniffnet · error · LatencyStatus::Failed

error.to_string()

Error message

error.to_string()

What it means

In measure_latency (src/networking/types/latency.rs:26), before pinging, client_for(ip) lazily builds a surge-ping ICMP Client (Client::new(&config) in latency_client, latency.rs:75). If constructing the client fails, the function immediately returns LatencyStatus::Failed(error.to_string()) and the error text is displayed next to the connection's RTT column. Typical cause: the OS refuses to create the ICMP socket, i.e. missing raw-socket privileges (Linux) or an unsupported/withdrawn IPv6 stack.

Source

Thrown at src/networking/types/latency.rs:26

const PING_TIMEOUT: Duration = Duration::from_secs(2);
const PING_PAYLOAD: [u8; 8] = [0; 8];
const PING_COUNT: usize = 3;

static IPV4_CLIENT: OnceLock<Arc<Client>> = OnceLock::new();
static IPV6_CLIENT: OnceLock<Arc<Client>> = OnceLock::new();
static PING_SEQUENCE: AtomicU16 = AtomicU16::new(0);

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LatencyStatus {
    Measuring,
    Measured(Duration),
    Failed(String),
}

pub async fn measure_latency(ip: IpAddr) -> LatencyStatus {
    let client = match client_for(ip) {
        Ok(client) => client,
        Err(error) => return LatencyStatus::Failed(error),
    };

    let mut pinger = client.pinger(ip, ping_identifier()).await;
    pinger.timeout(PING_TIMEOUT);

    let mut sum = Duration::ZERO;
    let mut received: u32 = 0;
    let mut last_error = None;
    for _ in 0..PING_COUNT {
        match pinger.ping(next_sequence(), &PING_PAYLOAD).await {
            Ok((_, latency)) => {
                sum += latency;
                received += 1;
            }
            Err(error @ SurgeError::Timeout { .. }) => last_error = Some(error.to_string()),
            Err(error) => {
                last_error = Some(error.to_string());
                break;

View on GitHub (pinned to 48b0575dc0)

Solutions

  1. Grant raw-socket capability to the binary: `sudo setcap cap_net_raw+eip $(which sniffnet)` (covers both capture and ICMP), or run with sudo.
  2. In containers, add the capability: `docker run --cap-add=NET_RAW ...`.
  3. For IPv6 failures, verify the stack is enabled (`sysctl net.ipv6.conf.all.disable_ipv6` should be 0) or disable IPv6 DNS resolution for the app.
  4. If privileges can't be changed, disable the latency-measurement feature in settings so the Failed text stops appearing.
  5. Check the exact text: it is the raw surge-ping/IO error (e.g. 'Operation not permitted (os error 1)'), which distinguishes privilege vs protocol-unavailable.

Example fix

# before — unprivileged binary, ICMP client creation fails
$ sniffnet   # latency column shows 'Failed: Operation not permitted (os error 1)'

# after — grant CAP_NET_RAW to the executable
$ sudo setcap cap_net_raw,cap_net_admin+eip $(readlink -f $(which sniffnet))
$ sniffnet   # latency values appear

# container equivalent
$ docker run --cap-add=NET_RAW --rm sniffnet
Defensive patterns

Strategy: try-catch

Validate before calling

// probe ICMP client creation once at startup and disable the feature if unavailable
fn latency_supported(ip_kind: icmp_ns::ICMP) -> bool {
    let config = match ip_kind {
        icmp_ns::ICMP::V4 => icmp_ns::Config::default(),
        icmp_ns::ICMP::V6 => icmp_ns::Config::builder().kind(icmp_ns::ICMP::V6).build(),
    };
    icmp_ns::Client::new(&config).is_ok()
}

Try / catch

// treat Failed as a display state, not an error path
match measure_latency(ip).await {
    LatencyStatus::Measured(d) => show_rtt(d),
    LatencyStatus::Measuring => show_spinner(),
    LatencyStatus::Failed(why) => show_muted(why), // e.g. 'Operation not permitted (os error 1)'

Prevention

When it happens

Trigger: Enabling latency measurement and clicking a host whose IP triggers first Client::new: IPv4 client creation fails when the process lacks CAP_NET_RAW/root (raw ICMP socket EPERM); IPv6 client creation fails with Config::builder().kind(ICMP::V6).build() on systems where IPv6 is disabled (socket creation error). Once a client fails, every subsequent measurement for that address family reuses the same failed path until the process restarts — actually it retries creation each time since only successful clients are cached in the OnceLock.

Common situations: Running Sniffnet unprivileged on Linux: capture may work via a setcap'd binary but the ping socket still needs privileges (or vice versa); running inside containers without CAP_NET_RAW; macOS hardened runtime blocking raw sockets; hosts with ipv6.disabled=1 kernel parameter showing Failed for all AAAA/IPv6 hosts; seccomp/container profiles denying socket(AF_INET, SOCK_RAW).

Related errors


AI-assisted analysis of GyulyVGC/sniffnet@48b0575dc0 (2026-08-16). Data as JSON: /api/errors/60533bb517f3dd04. Report an issue: GitHub.