Skip to content

KVM: packet capture of Instance NICs via operator-defined script - #13767

Open
wido wants to merge 1 commit into
apache:mainfrom
wido:packet-capture
Open

KVM: packet capture of Instance NICs via operator-defined script#13767
wido wants to merge 1 commit into
apache:mainfrom
wido:packet-capture

Conversation

@wido

@widowido commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Description

Adds root-admin-only APIs to capture the traffic of an Instance NIC on the KVM host it is running on, for debugging and lawful interception:

  • enablePacketCapture nicid=
  • disablePacketCapture nicid=
  • getPacketCaptureStatus nicid=

Packet capture is a flag on the NIC (stored as a nic detail). When enabled, the agent resolves the host-side tap device by MAC address, writes the NIC context (VM name/UUID, NIC UUID/MAC, IPv4/IPv6, bridge, network UUID) to /run/cloudstack/pcap-.env and starts the systemd template unit cloudstack-pcap@.service. The flag follows the Instance: a VM state listener restarts the capture after start and migration, and BindsTo= on the tap device stops the unit when the VM stops, migrates away or the NIC is unplugged.

The unit executes pcap-capture.sh, shipped between the other KVM scripts in /usr/share/cloudstack-common as an example that runs tcpdump writing to /tmp with 256 MB rotation. What to capture and what to do with the data differs too much per environment for CloudStack to decide it, so operators run their own script by copying the unit, pointing its ExecStart at it and naming their unit in packet.capture.service.

New agent.properties keys:

  • packet.capture.service (systemd template unit name)
  • packet.capture.env.dir (environment file directory)

This is in no way a "fits all" solution as capturing traffic will really differ per environment. I thought about storing it on Secondary Storage, but that would add so many different API calls and logic to CloudStack while in most cases every admin wants to do this differently. Therefor I have choosen this option.

Admins can override the packet capture script with a version that suits their environment.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Adds root-admin-only APIs to capture the traffic of an Instance NIC on
the KVM host it is running on, for debugging and lawful interception:
* enablePacketCapture nicid=<uuid>
* disablePacketCapture nicid=<uuid>
* getPacketCaptureStatus nicid=<uuid>
Packet capture is a flag on the NIC (stored as a nic detail). When
enabled, the agent resolves the host-side tap device by MAC address,
writes the NIC context (VM name/UUID, NIC UUID/MAC, IPv4/IPv6, bridge,
network UUID) to /run/cloudstack/pcap-<dev>.env and starts the systemd
template unit cloudstack-pcap@<dev>.service. The flag follows the
Instance: a VM state listener restarts the capture after start and
migration, and BindsTo= on the tap device stops the unit when the VM
stops, migrates away or the NIC is unplugged.
The unit executes pcap-capture.sh, shipped between the other KVM
scripts in /usr/share/cloudstack-common as an example that runs tcpdump
writing to /tmp with 256 MB rotation. What to capture and what to do
with the data differs too much per environment for CloudStack to decide
it, so operators run their own script by copying the unit, pointing its
ExecStart at it and naming their unit in packet.capture.service.
New agent.properties keys: packet.capture.service (systemd template
unit name) and packet.capture.env.dir (environment file directory).
@codecov

codecovBot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 3.41%. Comparing base (4f11707) to head (759bab3).

❗ There is a different number of reports uploaded between BASE (4f11707) and HEAD (759bab3). Click for more details.

HEAD has 1 upload less than BASE
FlagBASE (4f11707)HEAD (759bab3)
unittests10
Additional details and impacted files
@@ Coverage Diff @@## main #13767 +/- ##
=============================================
- Coverage 19.65% 3.41% -16.24% 
=============================================
Files 6368 487 -5881 Lines 574881 41867 -533014 Branches 70351 7912 -62439 =============================================
- Hits 112970 1429 -111541 + Misses 449639 40238 -409401 + Partials 12272 200 -12072 
FlagCoverage Δ
uitests3.41% <ø> (ø)
unittests?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a KVM-only packet-capture feature for instance NICs, exposed via new root-admin APIs, with the KVM agent starting/stopping a systemd template unit per captured NIC and writing NIC/VM context to an environment file for an operator-provided capture script.

Changes:

  • Adds new admin APIs (enablePacketCapture, disablePacketCapture, getPacketCaptureStatus) and server-side implementation that persists the “enabled” flag as a NIC detail and restarts capture on VM start/migration.
  • Implements the KVM agent-side command wrapper to resolve the host tap device by NIC MAC, write an env file, and control a systemd template unit.
  • Adds packaging artifacts: example capture script, a systemd template unit, agent.properties keys, and unit installation in RPM/Debian packaging.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
server/src/test/java/org/apache/cloudstack/network/packetcapture/PacketCaptureServiceImplTest.javaUnit tests for server-side packet capture enable/disable/status and VM state listener behavior.
server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xmlRegisters the new PacketCaptureServiceImpl in the server Spring context.
server/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureServiceImpl.javaImplements packet capture service, VM state listener restart logic, and agent command dispatch.
scripts/vm/hypervisor/kvm/pcap-capture.shExample capture script invoked by the systemd unit.
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPacketCaptureCommandWrapper.javaKVM agent wrapper that resolves NIC tap device, writes env, and controls systemd unit.
packaging/systemd/cloudstack-pcap@.serviceNew systemd template unit used to run per-NIC capture.
packaging/suse15/cloud.specInstalls the new systemd unit in SUSE RPM packaging.
packaging/el8/cloud.specInstalls the new systemd unit in EL8 RPM packaging.
debian/rulesInstalls the new systemd unit in Debian packaging.
core/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureCommand.javaNew management-to-agent command describing packet-capture action and NIC context.
core/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureAnswer.javaNew agent answer type including “running” status.
api/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureService.javaNew API service interface and NIC detail key constant.
api/src/main/java/org/apache/cloudstack/api/response/PacketCaptureResponse.javaNew API response object for packet capture status.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/GetPacketCaptureStatusCmd.javaNew admin API command to query capture enabled/running state.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/EnablePacketCaptureCmd.javaNew admin API command to enable capture on a NIC.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/DisablePacketCaptureCmd.javaNew admin API command to disable capture on a NIC.
api/src/main/java/com/cloud/event/EventTypes.javaAdds new event types for enable/disable packet capture.
agent/src/main/java/com/cloud/agent/properties/AgentProperties.javaAdds agent.properties keys for unit name and env directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +196 to +202
Answer answer = agentManager.easySend(vm.getHostId(), command);
if (answer == null || !answer.getResult()) {
throw new CloudRuntimeException(String.format("Failed to %s packet capture for NIC %s of VM %s on host %d: %s",
action.name().toLowerCase(), nic.getUuid(), vm.getInstanceName(), vm.getHostId(),
answer == null ? "no answer from host" : answer.getDetails()));
}
return (PacketCaptureAnswer) answer;
Comment on lines +145 to +159
if (vm.getHypervisorType() != HypervisorType.KVM || vm.getHostId() == null) {
return;
}
for (NicVO nic : nicDao.listByVmId(vm.getId())) {
if (!isPacketCaptureEnabled(nic.getId())) {
continue;
}
try {
VMInstanceVO vmVo = vmInstanceDao.findById(vm.getId());
sendCommand(PacketCaptureCommand.Action.START, vmVo, nic);
logger.info("Started packet capture on NIC {} of VM {} on host {}", nic, vm, vm.getHostId());
} catch (Exception e) {
logger.warn("Failed to start packet capture on NIC {} of VM {} on host {}", nic, vm, vm.getHostId(), e);
}
}
Comment on lines +37 to +41
[Service]
Type=simple
EnvironmentFile=/run/cloudstack/pcap-%i.env
ExecStart=/usr/share/cloudstack-common/scripts/vm/hypervisor/kvm/pcap-capture.sh
Restart=no
Comment on lines +150 to +153
Path file = getEnvironmentFile(nicDevice.getDevName());
Files.createDirectories(file.getParent());
Files.write(file, lines);
}
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@wido