Documentation
¶
Overview ¶
Package capture records packets in one container's network namespace with an opt-in packet-capture helper, retrieves the classic pcap file and checks its structure. It never interprets the packets: a structurally valid file does not show that the intended traffic was captured, that nothing was dropped, or that any protocol message in it is valid.
The helper meets contract 1 of the packet-capture extension of ceph-testcontainers-images. No helper image is published yet, so callers always name the image; the package never selects one by itself.
Index ¶
Constants ¶
const ( // MaxSnapLen bounds Request.SnapLen: libpcap's largest default snaplen. MaxSnapLen = 262144 // MaxFileBytes bounds the capture file that a request allows: // 24 + MaxPackets × (16 + SnapLen) bytes, as the helper contract derives // it. MaxFileBytes = 1 << 30 // MaxFilterBytes bounds Request.Filter. MaxFilterBytes = 4096 // DefaultReadyTimeout is the readiness wait of a zero Request.ReadyTimeout. DefaultReadyTimeout = 30 * time.Second // DefaultStopGrace is the graceful-stop wait of a zero Request.StopGrace. DefaultStopGrace = 10 * time.Second // MaxTimeout bounds Request.ReadyTimeout and Request.StopGrace. MaxTimeout = 5 * time.Minute )
Variables ¶
var ( // ErrImageUnsupported reports a helper image that does not meet the // capture contract: a missing sh, sleep, tcpdump or python3, a tcpdump // that does not run or compile a filter, or a helper that runs as root. // It belongs to ceph.ErrImageCapability. ErrImageUnsupported error = &categoryError{text: "the capture helper image does not meet the capture contract", kind: ceph.ErrImageCapability} // ErrPermissionDenied reports an engine that did not let the helper's // tcpdump run or open its device with NET_RAW. It belongs to // ceph.ErrEnvironmentUnavailable. ErrPermissionDenied error = &categoryError{text: "the capture helper was not permitted to capture packets", kind: ceph.ErrEnvironmentUnavailable} // ErrFailed reports a capture that did not start, end or deliver as the // contract says: tcpdump exited before readiness or with a nonzero // status, did not end within the stop grace, the target's network // namespace changed, or the helper's file could not be copied or is not // a valid classic pcap. It belongs to ceph.ErrNativeFailure. ErrFailed error = &categoryError{text: "the packet capture failed", kind: ceph.ErrNativeFailure} )
Functions ¶
This section is empty.
Types ¶
type EndReason ¶
type EndReason string
EndReason says how the capture process ended.
const ( // EndStopped means tcpdump ended with status 0 after Stop's SIGTERM. EndStopped EndReason = "stopped" // EndPacketLimit means tcpdump ended with status 0 before Stop, and the // valid file holds Request.MaxPackets packets (as does tcpdump's // captured counter when it printed one): later traffic is not in the // file. EndPacketLimit EndReason = "packet_limit" // EndPremature means tcpdump ended with status 0 before Stop, but the // file and its counter do not show that it reached Request.MaxPackets, // as after a signal from elsewhere: traffic after its end is missing. EndPremature EndReason = "premature" // EndKilled means tcpdump did not end within the stop grace and the // helper was killed; the file may hold only part of the capture. EndKilled EndReason = "killed" // EndFailed means tcpdump ended with a nonzero status. EndFailed EndReason = "failed" // EndUnknown means the end could not be observed, or that tcpdump ended // with status 0 before Stop and no valid file was retrieved to tell // EndPacketLimit from EndPremature. EndUnknown EndReason = "unknown" )
type FileReport ¶
type FileReport struct {
Status FileStatus `json:"status"`
// Reason explains any status other than FileValid.
Reason string `json:"reason,omitempty"`
// ByteOrder is "little" or "big"; TimestampUnit is "microsecond" or
// "nanosecond".
ByteOrder string `json:"byte_order,omitempty"`
TimestampUnit string `json:"timestamp_unit,omitempty"`
VersionMajor uint16 `json:"version_major,omitempty"`
VersionMinor uint16 `json:"version_minor,omitempty"`
SnapLen uint32 `json:"snaplen,omitempty"`
LinkType uint32 `json:"link_type,omitempty"`
Packets int `json:"packets"`
// TruncatedPackets counts packets captured shorter than their original
// length: snaplen truncation, not a malformed file.
TruncatedPackets int `json:"truncated_packets"`
// BytesRead is how much of the file was checked, and SHA256 is of exactly
// those bytes. Complete says the check reached the end of the file, so
// that SHA256 identifies the whole file.
BytesRead int64 `json:"bytes_read"`
SHA256 string `json:"sha256"`
Complete bool `json:"complete"`
}
FileReport is the structural check of the retrieved file. Counts cover the records read completely before the verdict.
The check reads with two bounds, the file size and one record's captured length. A record's declared length is compared with them before anything is read, and record data streams through the digest without being held, so a hostile length can neither allocate nor read its declared size. Read errors are returned as errors, never as a verdict.
type FileStatus ¶
type FileStatus string
FileStatus is the structural verdict on the retrieved file.
const ( // FileValid means every record header and its data fit the file. A // valid file can hold zero packets. FileValid FileStatus = "valid" // FileUnsupported means a recognized format that is not checked, such // as pcapng. FileUnsupported FileStatus = "unsupported" // FileMalformed means the file is not classic pcap or breaks its // structure, including a final record that was cut off. FileMalformed FileStatus = "malformed" // FileLimitExceeded means the helper's file is larger than the // request's bound; only the first MaxBytes were kept and checked. FileLimitExceeded FileStatus = "limit_exceeded" )
type Report ¶
type Report struct {
// ContainerID, Network, Interface and MACAddress identify the captured
// endpoint; SandboxID, TargetStartedAt and Namespace the network
// namespace generation that the helper joined.
ContainerID string `json:"container_id"`
Network string `json:"network"`
Interface string `json:"interface"`
MACAddress string `json:"mac_address"`
SandboxID string `json:"sandbox_id"`
TargetStartedAt string `json:"target_started_at"`
Namespace string `json:"namespace,omitempty"`
// HelperImage is the requested image, HelperImageID the image the helper
// ran, HelperPlatform its os/architecture and ToolVersion what
// tcpdump --version printed.
HelperImage string `json:"helper_image"`
HelperImageID string `json:"helper_image_id"`
HelperPlatform string `json:"helper_platform,omitempty"`
ToolVersion string `json:"tool_version,omitempty"`
// Filter, SnapLen, MaxPackets and MaxBytes are the requested selection
// and bounds; MaxBytes is derived from SnapLen and MaxPackets.
Filter string `json:"filter"`
SnapLen int `json:"snaplen"`
MaxPackets int `json:"max_packets"`
MaxBytes int64 `json:"max_bytes"`
// StartedAt is when the helper was started, ReadyAt when tcpdump
// reported that it listens and its file existed, StopRequestedAt when
// Stop began and EndedAt when the capture process ended.
StartedAt time.Time `json:"started_at"`
ReadyAt time.Time `json:"ready_at"`
StopRequestedAt time.Time `json:"stop_requested_at"`
EndedAt time.Time `json:"ended_at"`
// End and ExitCode say how tcpdump ended.
End EndReason `json:"end"`
ExitCode int `json:"exit_code"`
// Target compares the target at Stop with the generation joined.
Target TargetState `json:"target"`
// Statistics are tcpdump's counters; Stderr is its standard error, up
// to 64 KiB.
Statistics Statistics `json:"statistics"`
Stderr string `json:"stderr,omitempty"`
// Output is the delivered file, Retrieved whether the helper's file was
// copied into it and RetrievedBytes how many bytes it holds.
Output string `json:"output"`
Retrieved bool `json:"retrieved"`
RetrievedBytes int64 `json:"retrieved_bytes"`
// RetrievedSHA256 identifies all bytes successfully delivered to Output,
// independently of the structural check's possibly shorter prefix.
RetrievedSHA256 string `json:"retrieved_sha256,omitempty"`
// File is the structural check of Output; its Status is empty when
// nothing was retrieved.
File FileReport `json:"file"`
// HelperRemoved says the helper container was removed.
HelperRemoved bool `json:"helper_removed"`
}
Report describes one capture: what was requested, what the helper and the target did, and what was delivered. Process, target, retrieval and file outcomes are separate: a valid file can come from a capture that ended early or lost its target.
The report carries JSON field tags for delivery. The pcap itself holds payloads, addresses and identifiers that text and JSON redaction do not sanitize; keep capture files only where the caller deliberately selects them.
type Request ¶
type Request struct {
// HelperImage is an image that meets contract 1 of the packet-capture
// extension.
HelperImage string
// Network is the user-defined bridge network through which the target's
// endpoint is selected; bridge, host and none are refused.
Network string
// Interface is the target's interface on Network, inside the target's
// network namespace, such as "eth0". Start checks that its MAC address
// is that of the endpoint; any and lo are refused.
Interface string
// Filter is a pcap-filter expression that selects the traffic. It is
// compiled before the capture starts.
Filter string
// SnapLen bounds the bytes kept of each packet: 1 to MaxSnapLen.
SnapLen int
// MaxPackets ends the capture after that many packets. With SnapLen it
// bounds the file to 24 + MaxPackets × (16 + SnapLen) bytes, at most
// MaxFileBytes.
MaxPackets int
// Output is the path of the file that Stop writes the capture to. It must
// not exist; Start creates it, and its directory must exist, so parallel
// or repeated tests need distinct paths.
Output string
// ReadyTimeout bounds the wait for readiness; zero is
// DefaultReadyTimeout.
ReadyTimeout time.Duration
// StopGrace bounds the wait for tcpdump to end after SIGTERM; zero is
// DefaultStopGrace. After it, Stop kills the helper.
StopGrace time.Duration
}
Request selects one capture. Every field except the two timeouts is required: the package chooses no image, interface, filter or bound.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session owns one running capture helper. The caller owns the session and calls Stop, also after a failed workload: no cluster Terminate knows it. Stop is retryable.
func Start ¶
Start starts a capture in target's network namespace, on its endpoint on req.Network, and returns once tcpdump listens there. It is an opt-in extension: req.HelperImage names the capture helper, which joins the target's namespace (Docker network mode container:<target>) with NET_RAW as its only capability and runs as the image's own non-root user. The target keeps its image, capabilities and networking; it may be an owned Ceph daemon or a caller-owned container attached to that bridge. Host-network, stopped or paused targets and targets that share another container's namespace are refused.
Before the capture starts, a short-lived probe from the same image in the same namespace checks the contract's executables (sh, sleep, tcpdump, python3) and non-root user, runs tcpdump --version, compiles the filter with tcpdump -y EN10MB -d and reads the interface's MAC address. The capture then runs the image ID that the probe ran, not the tag, as
tcpdump -p -i <Interface> -s <SnapLen> -c <MaxPackets> --immediate-mode -U -w <file> -- <Filter>
A missing HelperImage is ceph.ErrImageCapability; a bad request, filter or interface is ceph.ErrInvalidRequest; and the errors ErrImageUnsupported, ErrPermissionDenied and ErrFailed tell an unsuitable image, a denied capability and a capture that did not become ready apart.
Readiness is tcpdump's "listening on <Interface>, link-type EN10MB" line together with its output file; a running helper or an existing file alone is not. Start the workload of interest only after Start returns. The session is bound to the target's container instance and sandbox at that point and never follows a restart. After a failure Start removes the helper and the output file it created. It returns a non-nil session with an error only when it could not remove a probe or a helper that failed; the empty output file then stays, and Stop retries the removal.
func (*Session) CaptureFile ¶
CaptureFile opens the settled session's original, caller-owned output for read-only archive copying. The size and SHA256 identify all retrieved bytes, not merely the structural validator's prefix. It does not stop the session or transfer ownership of its file. Close the returned reader.
Finalization.CaptureWithFile is the explicit consent to archive these raw, unsanitized packets; ordinary Finalization.Capture never opens this source. The archive copies only for a failure: it verifies EOF, the exact size and the full SHA256 before it publishes the copy as capture-<registration>-<name>.pcap, refuses a symlink or other non-regular source, and fails verification when the file changed after retrieval. Keep the file and its directory until the finalization returns: a t.TempDir registered after TerminateOnCleanup is removed first, so create it before registering the finalization or use t.ArtifactDir.
func (*Session) Settle ¶
Settle is Stop for a ceph.Finalization, which registers the session with Finalization.Capture. It returns the Report as an any value and, so that an archived report names no host path, with Output reduced to its base name. It shares Stop's behavior: the context's cancellation does not shorten it, it waits for a Stop that is already running, and a Stop that already finished is not repeated.
func (*Session) SettlementAllowance ¶
SettlementAllowance is the longest Settle runs: the stop grace plus the package's own two-minute Docker allowance (130 seconds with the default stop grace), whatever context Settle is given. It is what ceph.Finalization.Capture admits for this session. Callers that do not use a finalization call Stop instead of Settle.
func (*Session) Stop ¶
Stop ends the capture with SIGTERM and waits up to the stop grace for tcpdump to flush and close its file; after the grace it kills the helper (EndKilled). It then compares the target with the joined generation, copies the helper's file into Request.Output, checks its structure and removes the helper. Its Docker work uses a fresh bounded context, so a canceled or expired ctx does not leave the helper behind.
The report is returned also with an error. The error joins ErrFailed for a capture that failed, was killed, ended early on its own, lost its target or delivered a file that is not valid classic pcap, and a removal error. A capture that ended at MaxPackets is not an error; its End says so. After a removal error Stop can be called again; it returns the same report.
type Statistics ¶
type Statistics struct {
Captured *uint64 `json:"captured,omitempty"`
ReceivedByFilter *uint64 `json:"received_by_filter,omitempty"`
DroppedByKernel *uint64 `json:"dropped_by_kernel,omitempty"`
DroppedByInterface *uint64 `json:"dropped_by_interface,omitempty"`
}
Statistics are tcpdump's counters from its standard error. A nil field is a counter that tcpdump did not print: unknown, not zero.
type TargetState ¶
type TargetState string
TargetState compares the target at Stop with the one the capture joined.
const ( // TargetUnchanged means the same container instance and network sandbox. TargetUnchanged TargetState = "unchanged" // TargetChanged means the target restarted, stopped or got another // sandbox: the capture covers only the namespace it joined. TargetChanged TargetState = "changed" // TargetGone means the target container no longer exists. TargetGone TargetState = "gone" // TargetUnknown means the target could not be inspected. TargetUnknown TargetState = "unknown" )