{"record":{"id":"60533bb517f3dd04","repo":"GyulyVGC/sniffnet","slug":"error-to-string","errorCode":null,"errorMessage":"error.to_string()","messagePattern":"error\\.to_string\\(\\)","errorType":"exception","errorClass":"LatencyStatus::Failed","httpStatus":null,"severity":"error","filePath":"src/networking/types/latency.rs","lineNumber":26,"sourceCode":"const PING_TIMEOUT: Duration = Duration::from_secs(2);\nconst PING_PAYLOAD: [u8; 8] = [0; 8];\nconst PING_COUNT: usize = 3;\n\nstatic IPV4_CLIENT: OnceLock<Arc<Client>> = OnceLock::new();\nstatic IPV6_CLIENT: OnceLock<Arc<Client>> = OnceLock::new();\nstatic PING_SEQUENCE: AtomicU16 = AtomicU16::new(0);\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum LatencyStatus {\n    Measuring,\n    Measured(Duration),\n    Failed(String),\n}\n\npub async fn measure_latency(ip: IpAddr) -> LatencyStatus {\n    let client = match client_for(ip) {\n        Ok(client) => client,\n        Err(error) => return LatencyStatus::Failed(error),\n    };\n\n    let mut pinger = client.pinger(ip, ping_identifier()).await;\n    pinger.timeout(PING_TIMEOUT);\n\n    let mut sum = Duration::ZERO;\n    let mut received: u32 = 0;\n    let mut last_error = None;\n    for _ in 0..PING_COUNT {\n        match pinger.ping(next_sequence(), &PING_PAYLOAD).await {\n            Ok((_, latency)) => {\n                sum += latency;\n                received += 1;\n            }\n            Err(error @ SurgeError::Timeout { .. }) => last_error = Some(error.to_string()),\n            Err(error) => {\n                last_error = Some(error.to_string());\n                break;","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/GyulyVGC/sniffnet/blob/48b0575dc0d3c7e503a466a3cb2c1466ba600f8e/src/networking/types/latency.rs#L8-L44","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Grant raw-socket capability to the binary: `sudo setcap cap_net_raw+eip $(which sniffnet)` (covers both capture and ICMP), or run with sudo.","In containers, add the capability: `docker run --cap-add=NET_RAW ...`.","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.","If privileges can't be changed, disable the latency-measurement feature in settings so the Failed text stops appearing.","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."],"exampleFix":"# before — unprivileged binary, ICMP client creation fails\n$ sniffnet   # latency column shows 'Failed: Operation not permitted (os error 1)'\n\n# after — grant CAP_NET_RAW to the executable\n$ sudo setcap cap_net_raw,cap_net_admin+eip $(readlink -f $(which sniffnet))\n$ sniffnet   # latency values appear\n\n# container equivalent\n$ docker run --cap-add=NET_RAW --rm sniffnet","handlingStrategy":"try-catch","validationCode":"// probe ICMP client creation once at startup and disable the feature if unavailable\nfn latency_supported(ip_kind: icmp_ns::ICMP) -> bool {\n    let config = match ip_kind {\n        icmp_ns::ICMP::V4 => icmp_ns::Config::default(),\n        icmp_ns::ICMP::V6 => icmp_ns::Config::builder().kind(icmp_ns::ICMP::V6).build(),\n    };\n    icmp_ns::Client::new(&config).is_ok()\n}","typeGuard":null,"tryCatchPattern":"// treat Failed as a display state, not an error path\nmatch measure_latency(ip).await {\n    LatencyStatus::Measured(d) => show_rtt(d),\n    LatencyStatus::Measuring => show_spinner(),\n    LatencyStatus::Failed(why) => show_muted(why), // e.g. 'Operation not permitted (os error 1)'","preventionTips":["Grant CAP_NET_RAW to the binary (`setcap cap_net_raw+eip`) or run elevated so both capture and ICMP work.","In containers, always pass --cap-add=NET_RAW.","Verify IPv6 is enabled (`sysctl net.ipv6.conf.all.disable_ipv6`) before expecting IPv6 latencies.","Disable the latency feature in settings on hosts where ICMP sockets cannot be created."],"tags":["rust","sniffnet","icmp","ping","surge-ping","permissions","ipv6"],"backgroundTag":null,"analyzedSha":"48b0575dc0d3c7e503a466a3cb2c1466ba600f8e","analyzedAt":"2026-08-16T09:14:10.427Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}