stamparm/maltrail · error
capture error on worker
Error message
capture error on worker {} ({e}) What it means
This error is logged when the libpcap packet-capture read call inside the worker loop returns Err(e). In offline (pcap file replay) mode it means the capture file could not be read at all (or hit a malformed/truncated region); if zero packets were read the worker fails with WorkerError::Capture instead of silently reporting a successful replay of zero packets. In live-capture mode it means the capture handle hit an error (e.g. interface disappeared); only after LIVE_CAPTURE_ERROR_LIMIT consecutive errors does it become fatal.
Solutions
- Validate the capture file with tshark -r file or capinfos before replaying; fix the source file (e.g. re-merge with mergecap -F pcap so all interfaces share one link type).
- Check the log for '(no packets could be read)': if absent, packets were already processed and the error is only a truncated tail — treat the run's events as valid.
- For live captures, verify interface name and permissions (capabilities/root, or configure the ambient-capture capability); the failure becomes fatal only after repeated consecutive errors, so check whether LIVE_CAPTURE_ERROR_LIMIT was hit.
- Ensure the pcapng was written completely before replay; use the final, non-growing file.
Example fix
// before sensor replay merged.pcapng # exits 0, received=0, 'capture error on worker 0' // after mergecap -w merged.pcap merged1.pcapng merged2.pcapng # single link type / pcap format sensor replay merged.pcap # packets parsed, events produced
Defensive patterns
Strategy: validation
Validate before calling
import subprocess, sys
def validate_capture(path: str) -> bool:
"""Ensure libpcap/tshark can read the capture before replaying."""
r = subprocess.run(["capinfos", path], capture_output=True, text=True)
if r.returncode != 0:
print(f"unreadable capture: {r.stderr}", file=sys.stderr)
return False
if int(r.stdout.split('Number of packets:')[1].split()[0]) == 0:
print('capture has zero packets', file=sys.stderr)
return False
return True
assert validate_capture('merged.pcapng'), 'fix capture file before replay' Type guard
fn is_readable_capture(path: &str) -> bool {
pcap::Capture::from_file(path)
.map(|mut c| c.next_packet().is_ok())
.unwrap_or(false)
} Try / catch
match sensor.run_all() {
Ok(report) if report.received > 0 => { /* success */ }
Ok(report) => eprintln!("replay succeeded but 0 packets read — check capture file"),
Err(sensor::Error::Capture(msg)) => eprintln!("capture failed: {msg}"),
Err(e) => return Err(e.into()),
} Prevention
- Run capinfos/tshark -r on capture files before feeding them to the sensor
- Use mergecap with an explicit format (-F pcap) so all interfaces share one link type
- Never replay a capture file still being written (truncated tails)
- Verify interface permissions and link state before starting live capture
- Monitor for 'capture error on worker' log lines; treat zero-packet successful runs as a red flag
When it happens
Trigger: Calling run_all/run with an offline pcap/pcapng file that libpcap cannot read — e.g. a mergecap pcapng whose interfaces have different link types — or a truncated/corrupt capture file; or a live capture whose interface errors out (device gone, permission/privilege loss, BPF/capture handle failure).
Common situations: Replaying a mergecap-produced pcapng with mismatched per-interface link types; a capture file cut off mid-write (truncated tail after some packets); running a live capture and the NIC is removed or link state changes; insufficient privileges causing the capture handle to degrade.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13).
Data as JSON: /api/errors/44865da8522b8663.
Report an issue: GitHub.
Appendix: source
Thrown at sensor/src/worker.rs:225
handle = next;
label = next_label;
file_started = Instant::now();
file_packets = 0;
file_events = st.sink.events;
datalink = handle.datalink();
poll_fd = handle.selectable_fd();
continue;
}
None => {
outcome = Ok(WorkerExit::OfflineEof);
fatal = true;
}
}
}
break;
}
Err(e) => {
crate::output::log_error(&format!("capture error on worker {} ({e})", ctx.id), true);
if offline {
// A capture error is NOT a clean end of file. If it happened before a
// single packet was read, the capture could not be read at all, and
// reporting a successful replay of zero packets is the offline version
// of the silent blind spot Gate 1.1 fixed: the analyst sees "no
// detections" when the truth is "your file was never parsed".
//
// Found by the shadow harness on a `mergecap` output — libpcap refuses a
// pcapng whose interfaces have different link types, and the sensor
// replayed it to "success" with received=0.
//
// After packets HAVE been read this is a truncated tail: the events found
// so far are real and worth keeping, so the run still succeeds and the
// error stands in the log.
if st.metrics.packets_received == 0 {
outcome = Err(WorkerError::Capture(format!("{e} (no packets could be read)")));
} else {
outcome = Ok(WorkerExit::OfflineEof);View on GitHub (pinned to 77cfb06d76)