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

@wido wido 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

codecov Bot 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
Flag BASE (4f11707) HEAD (759bab3)
unittests 1 0
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     
Flag Coverage Δ
uitests 3.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.

Copilot AI 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
File Description
server/src/test/java/org/apache/cloudstack/network/packetcapture/PacketCaptureServiceImplTest.java Unit 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.xml Registers the new PacketCaptureServiceImpl in the server Spring context.
server/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureServiceImpl.java Implements packet capture service, VM state listener restart logic, and agent command dispatch.
scripts/vm/hypervisor/kvm/pcap-capture.sh Example capture script invoked by the systemd unit.
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPacketCaptureCommandWrapper.java KVM agent wrapper that resolves NIC tap device, writes env, and controls systemd unit.
packaging/systemd/cloudstack-pcap@.service New systemd template unit used to run per-NIC capture.
packaging/suse15/cloud.spec Installs the new systemd unit in SUSE RPM packaging.
packaging/el8/cloud.spec Installs the new systemd unit in EL8 RPM packaging.
debian/rules Installs the new systemd unit in Debian packaging.
core/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureCommand.java New management-to-agent command describing packet-capture action and NIC context.
core/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureAnswer.java New agent answer type including “running” status.
api/src/main/java/org/apache/cloudstack/network/packetcapture/PacketCaptureService.java New API service interface and NIC detail key constant.
api/src/main/java/org/apache/cloudstack/api/response/PacketCaptureResponse.java New API response object for packet capture status.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/GetPacketCaptureStatusCmd.java New admin API command to query capture enabled/running state.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/EnablePacketCaptureCmd.java New admin API command to enable capture on a NIC.
api/src/main/java/org/apache/cloudstack/api/command/admin/nic/DisablePacketCaptureCmd.java New admin API command to disable capture on a NIC.
api/src/main/java/com/cloud/event/EventTypes.java Adds new event types for enable/disable packet capture.
agent/src/main/java/com/cloud/agent/properties/AgentProperties.java Adds 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 free to 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