---
module: agent.packet-capture
title: Packet Capture — on-demand network capture for ATM diagnostics
tab: Architect
order: 30
audience: dev
---

> **Internal · how it's built.** Confirmed against `hiveops-module-packet-capture` source (`com.hiveops.packetcapture`) 2026-07-01. See [technical.agent] for agent-wide context.

## Maven module

`hiveops-module-packet-capture` (artifact `hiveops-module-packet-capture`, own version `1.0.0`, parented by `hiveops-agent-parent`). Packaged as a standalone jar (`<finalName>${project.artifactId}</finalName>` → `hiveops-module-packet-capture.jar`), depending only on `hiveops-core` (provided scope) — no dependency on `hiveops-journal`, `hiveops-images`, or `hiveops-file-collection`. It's listed in the parent `pom.xml` `<modules>` alongside the other module-set jars (`hiveops-module-log`, `hiveops-module-hw-inventory`, etc.).

Registered via ServiceLoader SPI: `src/main/resources/META-INF/services/com.hiveops.core.module.AgentModule` → `com.hiveops.packetcapture.PacketCaptureModule`.

## Classes and flow

Three classes, no sub-packages:

| Class | Role |
|---|---|
| `PacketCaptureModule` | `AgentModule` implementation — lifecycle, scheduler, poll loop |
| `PacketCaptureHandler` | Executes one `PACKET_CAPTURE` task: parses `configPatch`, runs the tool, uploads the result, reports status |
| `PacketCaptureTool` | Platform-specific process runner: picks and drives `tcpdump` (Linux) or `tshark`/`pktmon`/`netsh trace` (Windows, in that preference order) |

**Poll → detect → capture → upload** cycle (poll-based, not log-tail or hook-based):

1. `PacketCaptureModule.start()` schedules `poll()` on a single-thread daemon executor every **30s** (`POLL_INTERVAL_SECONDS`).
2. `poll()` calls `FileUploadClient.queryCurrentAtmPendingTask(country, atmName, "PACKET_CAPTURE")` — a **kind-filtered** poll (`GET /api/agent/{country}/{atm}/task?kind=PACKET_CAPTURE`), so this module only ever sees tasks of its own kind; it does not compete with `CommandProcessor`'s generic poll for other task kinds.
3. If a task is returned, it builds a `PacketCaptureHandler` and calls `execute(task)`. A static `AtomicBoolean captureInProgress` shared across polls ensures only one capture runs at a time per ATM — a second concurrent task is failed immediately with "Another capture is already in progress on this ATM".
4. `PacketCaptureHandler.doExecute()`:
   - Reads `duration` (default 60, clamped to `MAX_DURATION=300`), `interface` (default `"auto"`), `filter` (default empty) from `task.configPatch`.
   - Probes which capture tool/format will be used (`PacketCaptureTool.detectFormat()`) **before** creating the output file, so the extension (`.pcap` vs `.etl`) matches the actual tool up front.
   - Reports `RUNNING` via `statusReport`, then runs `PacketCaptureTool.capture()` synchronously, with a progress callback firing roughly every 10s that updates the task's status message with remaining seconds.
   - On success, uploads the file in `100_000`-byte chunks (`uploadFile()` / `FileUploadClient.fleetManagementUpload`), tracking a server-confirmed offset and retrying a stalled chunk up to 5 times before failing.
   - Always deletes the local capture file in a `finally` block, whether the task succeeded or failed.
5. `PacketCaptureTool.capture()` branches on OS (`System.getProperty("os.name")`):
   - **Linux:** requires `tcpdump` (`which tcpdump`); throws if missing. Runs `tcpdump -U -q [-i <iface>] -w <file> [filter tokens]` as a foreground process for the requested duration, then force-kills it.
   - **Windows:** tries `tshark` first (`-a duration:<n> -w <file> [-i <iface>] [-f <filter>]`, produces `.pcap`); falls back to `pktmon start --capture --file-name <file>` / `stop` (produces `.etl`, ignores the BPF filter — captures everything); falls back further to `netsh trace start capture=yes traceFile=<file> maxSize=50` / `stop` (also `.etl`, also ignores filter, and `stop` can take up to 90s to flush).
   - `abort()` (called from `PacketCaptureModule.stop()` on agent shutdown) force-kills the foreground process and, on Windows, best-effort stops `pktmon`/`netsh trace` too.

## How events/results reach the platform

This module does **not** integrate with the journal/incident event pipeline (`IncidentEventClient`, `EventType`) at all — see [technical.agent] "API surface" for that separate path. Its only platform integration is the **fleet task protocol**, shared with every other fleet-task-driven module (`command-processor`, log-collect, etc.). The agent-side calls (verified in `FileUploadClient`, `hiveops-core`) are:

```
PacketCaptureModule / PacketCaptureHandler
   │  (FileUploadClient, HttpClientSettings built from agent.endpoint/incident.endpoint)
   ▼
{endpoint}  GET  /api/agent/{country}/{atm}/task?kind=PACKET_CAPTURE   (poll)
            POST /api/agent/tasks/{tid}/status                          (RUNNING/COMPLETED/FAILED + msg)
            POST /api/agent/tasks/{tid}/upload?o=<base36offset>&f=<enc>  (chunked file upload)
   │  (endpoint = agent.endpoint → agent-proxy in current topology)
   ▼
agent-proxy → incident's internal fleet API → fleet task store / Fleet UI
   (downstream controller name + internal path unverified, cross-repo)
```

- `HttpClientSettings incidentSettings` is built once in `initialize()` via `HttpClientHelper.loadSettingsFromProperties(props, prefix)`, where `prefix` is `"agent"` if `agent.endpoint` is set, else `"incident"`. This is the same pattern used by other modules that need to talk to the fleet/incident base URL.
- Endpoints hit (all via `FileUploadClient`, defined in `hiveops-core`):
  - `GET {endpoint}/api/agent/{country}/{atm}/task?kind=PACKET_CAPTURE` — poll
  - `POST {endpoint}/api/agent/tasks/{tid}/status` — RUNNING/COMPLETED/FAILED + message
  - `POST {endpoint}/api/agent/tasks/{tid}/upload?o=...&f=...` — chunked capture-file upload

## Platform abstraction

The module does **not** use `com.hiveops.platform.PlatformServiceFactory` / `PlatformService` at all — it does its own OS detection (`PacketCaptureTool.isWindows()` checking `os.name`) and shells out directly via `ProcessBuilder`, rather than going through the shared `SystemCommands` abstraction other modules (e.g. `file-collection`'s reboot/patch handling) use. This is a deliberate/simple design: the module only ever needs to run one of four well-known external tools and doesn't need registry/config-file reads or path resolution.

## Coordination with the legacy command-processor

`CommandProcessor` (in `hiveops-file-collection`) also long-polls the server for pending tasks (unfiltered by kind). When its poll loop encounters a `PACKET_CAPTURE` task, it explicitly does **not** process it — it reports the task back to `PENDING` ("Released to packet-capture module") and sleeps, leaving it for this module's kind-filtered poll to pick up. This avoids two different modules racing to claim the same task.
