KVM: packet capture of Instance NICs via operator-defined script - #13767
KVM: packet capture of Instance NICs via operator-defined script#13767wido wants to merge 1 commit into
Conversation
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 Report✅ All modified and coverable lines are covered by tests.
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.
| 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; |
| 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); | ||
| } | ||
| } |
| [Service] | ||
| Type=simple | ||
| EnvironmentFile=/run/cloudstack/pcap-%i.env | ||
| ExecStart=/usr/share/cloudstack-common/scripts/vm/hypervisor/kvm/pcap-capture.sh | ||
| Restart=no |
| Path file = getEnvironmentFile(nicDevice.getDevName()); | ||
| Files.createDirectories(file.getParent()); | ||
| Files.write(file, lines); | ||
| } |
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:
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:
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
Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale
Bug Severity