framework

package
v0.0.0-...-4f1e891 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 15, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// MAC addresses used in test framework
	SrcMAC = "52:54:00:6b:ff:a1"
	DstMAC = "52:54:00:6b:ff:a5"

	// IP addresses used in test framework
	VMIPv4Host    = "203.0.113.14"
	VMIPv4Gateway = "203.0.113.1"
	VMIPv6Gateway = "fe80::1"
	VMIPv6Host    = "fe80::5054:ff:fe6b:ffa5"

	// CLI tool paths (9P defaults, used in single-VM mode)
	CLIBasePath    = "/mnt/target/release"
	CLIRoute       = CLIBasePath + "/yanet-cli-route"
	CLIRouteMPLS   = CLIBasePath + "/yanet-cli-route-mpls"
	CLINAT64       = CLIBasePath + "/yanet-cli-nat64"
	CLIACL         = CLIBasePath + "/yanet-cli-acl"
	CLIFWState     = CLIBasePath + "/yanet-cli-fwstate"
	CLIFWStateMap  = CLIBasePath + "/yanet-cli-fwstatemap"
	CLIPipeline    = CLIBasePath + "/yanet-cli-pipeline"
	CLIFunction    = CLIBasePath + "/yanet-cli-function"
	CLIDevicePlain = CLIBasePath + "/yanet-cli-device-plain"
	CLIDecap       = CLIBasePath + "/yanet-cli-decap"
	CLIForward     = CLIBasePath + "/yanet-cli-forward"
	CLIGeneric     = CLIBasePath + "/yanet-cli"
)
View Source
const (
	// BootedSnapshotName is the name of the QEMU snapshot saved after a
	// clean boot. When a pool VM's overlay contains this snapshot, Start()
	// restores it instantly via -loadvm instead of waiting ~44s for boot.
	BootedSnapshotName = "booted"
)
View Source
const ReadTimeout = 2 * time.Second

ReadTimeout is the per-read timeout SendAndReceivePacket waits for a single reply.

Measured against a CPU-starved CI runner across roughly 38000 sends, reply latency was p50 10ms and p99 53ms, and the single miss observed still arrived at 114ms rather than being lost. The timeout gives roughly 18x headroom over that observed maximum.

Variables

View Source
var CLIBinaryNames = []string{
	"yanet-cli",
	"yanet-cli-route", "yanet-cli-route-mpls",
	"yanet-cli-nat64", "yanet-cli-acl", "yanet-cli-blackhole",
	"yanet-cli-fwstate", "yanet-cli-fwstatemap", "yanet-cli-pipeline", "yanet-cli-function",
	"yanet-cli-device-plain", "yanet-cli-device-vlan",
	"yanet-cli-decap", "yanet-cli-forward",
	"yanet-cli-common", "yanet-cli-dscp", "yanet-cli-counters",
	"yanet-cli-pdump", "yanet-cli-inspect",
}

CLIBinaryNames is the canonical list of all CLI binaries installed in the YANET target/release directory. Used by StartYANET verification, PrepareLocalStorage, and test assertions to keep binary lists in sync.

View Source
var CmpStdOpts = []cmp.Option{
	cmpopts.IgnoreUnexported(
		layers.Ethernet{},
		layers.Dot1Q{},
		layers.IPv4{},
		layers.IPv6{},
		layers.TCP{},
		layers.UDP{},
		layers.ICMPv4{},
		layers.ICMPv6{},
		layers.ICMPv6Echo{},
		layers.ICMPv6RouterSolicitation{},
		layers.ICMPv6RouterAdvertisement{},
		layers.ICMPv6NeighborSolicitation{},
		layers.ICMPv6NeighborAdvertisement{},
		layers.IPv6Destination{},
		layers.IPv6Routing{},
		layers.IPv6HopByHop{},
		layers.IPv6Fragment{},
		layers.GRE{},
		layers.MPLS{},
		layers.ARP{},
		gopacket.DecodeFailure{},
	),
	cmpopts.IgnoreFields(layers.Ethernet{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.Dot1Q{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv4{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv6{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.TCP{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.UDP{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv4{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6Echo{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6RouterSolicitation{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6RouterAdvertisement{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6NeighborSolicitation{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ICMPv6NeighborAdvertisement{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv6Destination{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv6Routing{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv6HopByHop{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.IPv6Fragment{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.GRE{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.MPLS{}, "BaseLayer"),
	cmpopts.IgnoreFields(layers.ARP{}, "BaseLayer"),
}

CmpStdOpts provides standard cmp.Diff options for comparing gopacket layers, ensuring consistent packet comparison across functional tests.

These options:

  • Ignore unexported fields in gopacket layer structs (internal state)
  • Ignore BaseLayer fields (gopacket internal bookkeeping)
  • Ignore DecodeFailure unexported fields (for malformed packet handling)

Usage:

diff := cmp.Diff(expectedPkt.Layers(), actualPkt.Layers(), framework.CmpStdOpts...)
if diff != "" {
    t.Errorf("Packet mismatch (-want +got):\n%s", diff)
}
View Source
var CommonConfigCommands = (&TestFramework{Paths: DefaultGuestPaths()}).CommonConfigCommands()

CommonConfigCommands is the package-level variable for backward compatibility. Uses default 9P paths. In pool mode tests use fw.CommonConfigCommands() instead.

View Source
var ErrCaptureTimeout = errors.New("packet capture timed out")

ErrCaptureTimeout indicates that packet transmission succeeded but no packet was captured before the receive deadline.

Functions

func BaselineImagePath

func BaselineImagePath(baseImagePath string) string

BaselineImagePath returns the versioned path to the cached baseline snapshot.

func BootedImagePath

func BootedImagePath(baseImagePath string) string

BootedImagePath returns the versioned path to the booted snapshot image.

func CreateICMPv4EchoPacket

func CreateICMPv4EchoPacket(srcIP, dstIP net.IP, id, seq uint16, payload []byte) []byte

CreateICMPv4EchoPacket builds a serialized Ethernet/IPv4/ICMP Echo Request.

func CreateTCPIPv4Packet

func CreateTCPIPv4Packet(srcIP, dstIP net.IP, payload []byte, opts *TCPPacketOpts) []byte

CreateTCPIPv4Packet builds a serialized Ethernet/IPv4/TCP packet. Pass nil for opts to use defaults (SrcMAC/DstMAC from framework constants, SrcPort 12345, DstPort 80, PSH+ACK flags).

func CreateTCPIPv6Packet

func CreateTCPIPv6Packet(srcIP, dstIP net.IP, payload []byte, opts *TCPPacketOpts) []byte

CreateTCPIPv6Packet builds a serialized Ethernet/IPv6/TCP packet. Pass nil for opts to use defaults (same as CreateTCPIPv4Packet).

func DataplaneConfig

func DataplaneConfig(opts DataplaneOptions) string

DataplaneConfig returns the baseline dataplane YAML used by functional tests, optionally requesting runtime module plugins via opts.

func DefaultControlplaneConfig

func DefaultControlplaneConfig() string

DefaultControlplaneConfig returns the baseline controlplane YAML used by functional tests.

func DefaultForwardConfig

func DefaultForwardConfig() string

DefaultForwardConfig returns the baseline forward.yaml used by functional tests.

func DefaultRouteConfig

func DefaultRouteConfig() string

DefaultRouteConfig returns the baseline route0.yaml used by functional tests.

func ExpandCIDR

func ExpandCIDR(cidr string) []string

ExpandCIDR expands a CIDR notation to all IP addresses in the subnet This mimics Scapy's behavior where IP(dst="172.20.29.5/30") generates packets for 172.20.29.5, 172.20.29.6, 172.20.29.7, 172.20.29.8

func Fragment

func Fragment(pkt gopacket.Packet, fragSize int) ([]gopacket.Packet, error)

Fragment fragments an IPv4 packet according to RFC 791 fragSize is the maximum size of each fragment (including Ethernet/VLAN/IPv4 headers)

func Fragment6

func Fragment6(pkt gopacket.Packet, fragSize int) ([]gopacket.Packet, error)

Fragment6 fragments an IPv6 packet according to RFC 8200 fragSize is the maximum size of each fragment (including IPv6 header and fragment header)

func HasBaselineSnapshot

func HasBaselineSnapshot() bool

HasBaselineSnapshot returns true if the "baseline" VM snapshot is available for per-test state isolation via RunWith("baseline", ...).

func HasBootedSnapshot

func HasBootedSnapshot(overlayPath string) bool

HasBootedSnapshot checks if the given overlay file contains a snapshot named "booted". Returns true if the snapshot exists.

func IsDebugEnabled

func IsDebugEnabled() bool

IsDebugEnabled checks if debug mode is enabled for tests. Returns true if the YANET_TEST_DEBUG environment variable is set.

Example:

if IsDebugEnabled() {
    log.Info("Debug mode enabled")
    // Enable verbose logging
}

func KVMAvailable

func KVMAvailable() bool

KVMAvailable reports whether the current process can use the Linux KVM device.

func MarkBaselineSaved

func MarkBaselineSaved()

MarkBaselineSaved records that the "baseline" VM snapshot was successfully created. Called once from TestMain after setup completes.

func MustParseMAC

func MustParseMAC(mac string) net.HardwareAddr

MustParseMAC parses a MAC address string and panics if parsing fails. This utility function is designed for use with known-good MAC address constants where parsing failure indicates a programming error rather than runtime input error.

Parameters:

  • mac: MAC address string in standard format (e.g., "52:54:00:6b:ff:a1")

Returns:

  • net.HardwareAddr: Parsed hardware address

Panics:

  • If the MAC address string is malformed or invalid

Example:

hwAddr := MustParseMAC("52:54:00:6b:ff:a1")

func MustPrefixRange

func MustPrefixRange(prefix string) (start, end string)

MustPrefixRange is PrefixRange, panicking on a malformed prefix.

For call sites converting a compile-time-known CIDR literal, matching this package's existing MustParseMAC panic-on-literal convention.

func NewPacket

func NewPacket(opts *gopacket.SerializeOptions, layerBuilders ...LayerBuilder) (gopacket.Packet, error)

NewPacket creates a new gopacket.Packet from the given LayerBuilder chain, inferring protocol numbers and EtherType/VLAN types from layer order and leaving checksums/lengths untouched unless a custom layer requests otherwise. If opts is nil, default options (FixLengths: true, ComputeChecksums: true) are used.

func OverlayHasSnapshot

func OverlayHasSnapshot(imagePath, name string) bool

OverlayHasSnapshot returns true if the given qcow2 image contains a snapshot with the given name.

func Payload

func Payload(content string, repeat int) []byte

Payload creates a repeated byte slice from string content

func PoolSize

func PoolSize() int

PoolSize returns the desired VM pool size from the environment.

func PortRange

func PortRange(start, end uint16) []uint16

PortRange generates a slice of ports from start to end (inclusive)

func PrefixRange

func PrefixRange(prefix string) (start, end string, err error)

PrefixRange converts a CIDR prefix into its masked start and end address strings, matching the FIB YAML's range-native "start"/"end" keys.

It masks host bits before taking the start address, so an unmasked prefix such as "10.0.0.5/24" yields "10.0.0.0" rather than "10.0.0.5" as the range start. The end address is derived from the same masked network.

func ShouldKeepVMAlive

func ShouldKeepVMAlive() bool

ShouldKeepVMAlive checks if the VM should be kept running after tests. Returns true if the YANET_KEEP_VM_ALIVE environment variable is set. When enabled, the QEMU process will not be killed, allowing manual connection to the serial console for debugging.

Example:

if ShouldKeepVMAlive() {
    log.Info("VM will remain running for manual debugging")
    // Skip QEMU process termination
}

func SnapshotImagePath

func SnapshotImagePath(baseImagePath string, snapshotName string) string

SnapshotImagePath returns the path to the cached image containing the named snapshot next to the base image.

func VMReadyTimeout

func VMReadyTimeout() time.Duration

VMReadyTimeout returns how long to wait for a VM to reach the shell prompt.

It defaults to 120 seconds and is overridable via YANET_VM_READY_TIMEOUT, a Go duration string such as "5m" or "300s". An unset, empty, unparseable, or non-positive value falls back to the default, which slow TCG (no-KVM) runners routinely need to raise.

Types

type ARPBuilder

type ARPBuilder struct {
	// contains filtered or unexported fields
}

func ARP

func ARP(opts ...ARPOption) *ARPBuilder

func (*ARPBuilder) Build

type ARPOption

type ARPOption func(*ARPBuilder)

func ARPHwDst

func ARPHwDst(mac string) ARPOption

func ARPHwLen

func ARPHwLen(hwLen uint8) ARPOption

func ARPHwSrc

func ARPHwSrc(mac string) ARPOption

func ARPHwType

func ARPHwType(hwType uint16) ARPOption

func ARPOperation

func ARPOperation(op uint16) ARPOption

func ARPPDst

func ARPPDst(ip string) ARPOption

func ARPPLen

func ARPPLen(pLen uint8) ARPOption

func ARPPSrc

func ARPPSrc(ip string) ARPOption

func ARPPType

func ARPPType(pType uint16) ARPOption

type CLIManager

type CLIManager struct {
	// contains filtered or unexported fields
}

CLIManager handles YANET CLI operations within a QEMU virtual machine environment. It provides thread-safe command execution capabilities through the VM's serial console, with support for output buffering, command synchronization, and proper cleanup of control characters from terminal output.

The manager uses markers to reliably detect command completion and extract return codes, ensuring accurate command execution status reporting even in concurrent scenarios.

func NewCLIManager

func NewCLIManager(qemu *QEMUManager, opts ...CLIOption) (*CLIManager, error)

NewCLIManager creates and initializes a new CLI manager instance for executing commands within a QEMU virtual machine. The manager provides thread-safe command execution with proper output handling and error reporting.

Parameters:

  • qemu: A QEMUManager instance representing the target virtual machine
  • opts: Optional functional options for customizing the CLI manager behavior

Returns:

  • *CLIManager: A configured CLI manager instance
  • error: An error if initialization fails or options cannot be applied

func (*CLIManager) ExecuteCommand

func (c *CLIManager) ExecuteCommand(command string) (string, error)

ExecuteCommand executes a single CLI command within the QEMU virtual machine via the serial console interface. The method ensures thread-safe execution, proper output collection, and reliable command completion detection using unique markers.

The execution process includes:

  • VM readiness verification
  • Command wrapping with start/end markers for reliable parsing
  • Output buffering and cleanup of control characters
  • Return code extraction and error handling

Parameters:

  • command: The shell command to execute in the virtual machine

Returns:

  • string: The cleaned command output (stdout/stderr combined)
  • error: An error if the VM is not ready, command fails, or timeout occurs

Example:

output, err := cli.ExecuteCommand("ls -la /etc")
if err != nil {
    log.Fatalf("Command failed: %v", err)
}
fmt.Println("Directory listing:", output)

func (*CLIManager) ExecuteCommandWithTimeout

func (c *CLIManager) ExecuteCommandWithTimeout(command string, timeout time.Duration) (string, error)

ExecuteCommandWithTimeout is like ExecuteCommand but with a custom timeout.

func (*CLIManager) ExecuteCommands

func (c *CLIManager) ExecuteCommands(commands ...string) ([]string, error)

ExecuteCommands executes multiple CLI commands sequentially within the QEMU virtual machine. Each command is executed in order, and execution stops at the first command that returns an error.

This method is useful for executing a series of related commands where each command may depend on the success of previous ones, such as configuration setup or multi-step operations.

Parameters:

  • commands: Variable number of shell commands to execute sequentially

Returns:

  • []string: Slice of command outputs in execution order (may be partial if error occurs)
  • error: An error from the first failed command, or nil if all commands succeed

Example:

outputs, err := cli.ExecuteCommands(
    "mkdir -p /tmp/test",
    "echo 'hello' > /tmp/test/file.txt",
    "cat /tmp/test/file.txt",
)
if err != nil {
    log.Fatalf("Command sequence failed: %v", err)
}

func (*CLIManager) ExecuteCommandsSeparately

func (c *CLIManager) ExecuteCommandsSeparately(commands ...string) ([]string, []error)

ExecuteCommandsSeparately runs every command to completion regardless of individual failures, pairing each output with its own error. A diagnostics dump uses this so one failed probe (say, catting a log the crashed process never wrote) cannot hide the outputs of the rest.

func (*CLIManager) WithLog

func (c *CLIManager) WithLog(log *zap.SugaredLogger) *CLIManager

WithLog creates a new CLIManager instance with a different logger while sharing the same underlying connection (inner state). This allows each test to have its own logging context while sharing the CLI manager connection.

Parameters:

  • log: Logger to use for this CLI manager instance

Returns:

  • *CLIManager: A new CLI manager instance with the specified logger

Example:

namedCLI := cli.WithLog(logger.Named("test1"))

type CLIOption

type CLIOption func(*CLIManager) error

CLIOption defines functional options for configuring CLIManager instances. This pattern allows for flexible initialization with optional parameters while maintaining backward compatibility.

func CLIWithLog

func CLIWithLog(log *zap.SugaredLogger) CLIOption

CLIWithLog configures the CLIManager to use the specified logger for debugging and monitoring command execution. If not provided, a no-op logger is used by default.

Parameters:

  • log: A zap.SugaredLogger instance for logging CLI operations

Returns:

  • CLIOption: A functional option that sets the logger

type Config

type Config struct {
	Name        string
	QEMUImage   string // Path to the QEMU virtual machine image file
	ProjectRoot string // Canonical project root for build and target paths
}

Config contains essential configuration parameters for initializing the test framework. It specifies the QEMU virtual machine image and working directory for test execution.

type DataplaneOptions

type DataplaneOptions struct {
	PluginDir         string
	Modules           []string
	PacketRecircLimit uint16
}

DataplaneOptions customizes the baseline dataplane configuration a Harness boots with.

PluginDir and Modules drive the dataplane's runtime plugin loader: when PluginDir is set the dataplane scans it for module .so plugins and loads each name listed in Modules. Leaving both empty produces a configuration that relies solely on the modules statically linked into the dataplane binary.

type Dot1QBuilder

type Dot1QBuilder struct {
	// contains filtered or unexported fields
}

func Dot1Q

func Dot1Q(opts ...Dot1QOption) *Dot1QBuilder

func (*Dot1QBuilder) Build

type Dot1QOption

type Dot1QOption func(*layers.Dot1Q)

func VLANId

func VLANId(id uint16) Dot1QOption

func VLANType

func VLANType(etherType layers.EthernetType) Dot1QOption

type EtherOption

type EtherOption func(*layers.Ethernet)

func EtherDst

func EtherDst(mac string) EtherOption

func EtherSrc

func EtherSrc(mac string) EtherOption

func EtherType

func EtherType(etherType layers.EthernetType) EtherOption

type EthernetBuilder

type EthernetBuilder struct {
	// contains filtered or unexported fields
}

func Ether

func Ether(opts ...EtherOption) *EthernetBuilder

func (*EthernetBuilder) Build

type Framework

type Framework struct {
	// contains filtered or unexported fields
}

Framework is a safe wrapper that prevents direct access to framework methods. It provides two ways to access the framework:

  • Global() - returns TestFramework with testName="global" for global operations in TestMain
  • ForTest(t) - returns TestFramework bound to *testing.T for test-specific operations

This design ensures that framework methods cannot be called without proper context.

Example usage:

var globalFramework *Framework

func TestMain(m *testing.M) {
    fw, _ := New(config)
    globalFramework = fw
    globalFramework.Global().Start()
    defer globalFramework.Global().Stop()
    m.Run()
}

func TestMyFeature(t *testing.T) {
    fw := globalFramework.ForTest(t)
    fw.Run("SubTest", func(fw *TestFramework, t *testing.T) {
        // Use fw for framework operations, t for assertions
    })
}

func New

func New(config *Config, opts ...FrameworkOption) (*Framework, error)

New creates and initializes a new Framework instance with the specified configuration and optional functional parameters. The framework sets up all necessary components including QEMU VM management, CLI operations, and packet processing capabilities.

The initialization process includes:

  • Working directory creation and validation
  • QEMU manager setup with the specified VM image
  • CLI manager initialization for VM command execution
  • Packet parser setup for network analysis
  • Socket client cache initialization

Parameters:

  • config: Required configuration containing VM image path and working directory
  • opts: Optional functional options for customizing framework behavior

Returns:

  • *Framework: Fully initialized framework wrapper
  • error: An error if initialization fails or configuration is invalid

Example:

config := &Config{
    Name: "main",
    QEMUImage: "/path/to/vm-image.qcow2",
}
fw, err := New(config, WithLog(logger))
if err != nil {
    log.Fatalf("Failed to create framework: %v", err)
}

func (*Framework) ForTest

func (f *Framework) ForTest(t *testing.T) *TestFramework

ForTest creates a TestFramework instance bound to the provided *testing.T. This method should be called at the beginning of each test function to create a test-specific framework instance with automatic test name tracking.

Parameters:

  • t: The *testing.T instance for the current test

Returns:

  • *TestFramework: A test-bound framework instance

Example:

func TestMyFeature(t *testing.T) {
    fw := globalFramework.ForTest(t)
    fw.Run("SubTest", func(fw *TestFramework, t *testing.T) {
        // Test code here
    })
}

func (*Framework) Global

func (f *Framework) Global() *TestFramework

Global returns the underlying TestFramework with testName="global" for global operations. This method should only be used in TestMain for framework lifecycle management (Start, Stop, StartYANET, etc.).

Returns:

  • *TestFramework: The internal framework instance with testName="global"

Example:

func TestMain(m *testing.M) {
    fw, _ := New(config)
    globalFramework = fw
    gfw := fw.Global()
    gfw.Start()
    defer gfw.Stop()
    m.Run()
}

type FrameworkOption

type FrameworkOption func(*TestFramework) error

FrameworkOption defines functional options for configuring TestFramework instances. This pattern enables flexible initialization with optional parameters while maintaining backward compatibility and clean API design.

func WithLog

func WithLog(log *zap.SugaredLogger) FrameworkOption

WithLog configures the TestFramework to use the specified logger for debugging and monitoring test execution. This functional option allows detailed logging of framework operations, packet flows, and VM interactions.

Parameters:

  • log: A zap.SugaredLogger instance for structured logging

Returns:

  • FrameworkOption: A functional option that sets the logger

type GREBuilder

type GREBuilder struct {
	// contains filtered or unexported fields
}

func GRE

func GRE(opts ...GREOption) *GREBuilder

func (*GREBuilder) Build

type GREOption

type GREOption func(*GREBuilder)

func GREChecksum

func GREChecksum(checksum uint16) GREOption

func GREChecksumPresent

func GREChecksumPresent(present bool) GREOption

func GREKey

func GREKey(key uint32) GREOption

func GREKeyPresent

func GREKeyPresent(present bool) GREOption

func GREProtocol

func GREProtocol(proto layers.EthernetType) GREOption

func GRERawFlags

func GRERawFlags(flags uint16) GREOption

func GRERoutingPresent

func GRERoutingPresent(present bool) GREOption

func GRESeq

func GRESeq(seq uint32) GREOption

func GRESeqPresent

func GRESeqPresent(present bool) GREOption

func GREVersion

func GREVersion(version uint8) GREOption

type GuestPaths

type GuestPaths struct {
	CLIBase        string // directory with yanet-cli-* binaries
	BuildDir       string // directory with yanet-dataplane, yanet-controlplane
	PluginDir      string // directory with dataplane module .so plugins
	ConfigDir      string // directory for config files
	LogDir         string // directory for log files
	DPDKDevbindDir string // directory containing dpdk-devbind.py
	ForwardYAML    string // path to forward.yaml
	LocalMode      bool   // true when paths are on guest tmpfs (pool/snapshot mode)
}

GuestPaths holds all guest-side filesystem paths used by the framework. In single-VM mode these point to 9P mounts. In pool/snapshot mode they point to /tmp/yanet/ (local tmpfs) so that no YANET process holds open fids on 9P mounts, allowing savevm/loadvm to succeed.

func DefaultGuestPaths

func DefaultGuestPaths() GuestPaths

DefaultGuestPaths returns the standard 9P-backed paths used in single-VM mode.

func LocalGuestPaths

func LocalGuestPaths() GuestPaths

LocalGuestPaths returns paths on local tmpfs inside the guest. Used in pool/snapshot mode after PrepareLocalStorage() copies files from 9P mounts to /tmp/yanet/.

func (GuestPaths) CLI

func (p GuestPaths) CLI(name string) string

CLI returns the full path to a CLI binary by name.

type Harness

type Harness struct {
	// contains filtered or unexported fields
}

Harness owns a booted, baseline-configured VM pool shared by the tests of one functional-test package, together with the YANET configuration used to restore a VM to that baseline.

func SetupHarness

func SetupHarness(config HarnessConfig) (_ *Harness, cleanup func(), err error)

SetupHarness prepares a baseline VM pool for a functional-test package.

It ensures the baseline template overlay exists (bootstrapping it from the booted template when missing), starts the pool, waits for readiness, and pauses idle VM CPUs. The returned cleanup releases the harness logger; the caller owns Shutdown of the returned Harness.

func (*Harness) Pool

func (m *Harness) Pool() *VMPool

Pool returns the underlying VM pool.

func (*Harness) Restore

func (m *Harness) Restore(fw *TestFramework) error

Restore restores fw to a working YANET baseline without depending on the testing package. Lab tools use this method to get the same fast-path and fallback behavior as functional tests.

The fast path restores the "baseline" snapshot via RestoreClean, resets connections, and checks datapath readiness. The fallback restores the "preyanet" snapshot and starts YANET from scratch. Functional tests that pass no hooks get a fast path that differs from the older RestoreBooted contract (which used RestoreAndReconnect); the fallback path is unchanged.

func (*Harness) RestoreBooted

func (m *Harness) RestoreBooted(t *testing.T, fw *TestFramework)

RestoreBooted restores fw to a working YANET state.

It tries the fast path (baseline snapshot with YANET already running) first, and falls back to the slow path (preyanet snapshot plus a fresh StartYANET) only when the baseline restore fails.

func (*Harness) Shutdown

func (m *Harness) Shutdown() error

Shutdown stops every VM in the pool.

func (*Harness) WithBootedVM

func (m *Harness) WithBootedVM(t *testing.T, fn func(fw *TestFramework))

WithBootedVM acquires a pooled VM, restores it to a working YANET baseline, runs fn, then returns the VM to the pool.

type HarnessConfig

type HarnessConfig struct {
	PoolName         string
	PoolSize         int
	BaselineTag      string
	QEMUImage        string
	ProjectRoot      string
	Dataplane        string
	Controlplane     string
	Forward          string
	Route            string
	EnableSSHForward bool
	Prepare          func(*TestFramework) error
	AfterStart       func(*TestFramework) error
	ProfileReady     func(*TestFramework) error
	FingerprintFiles []string
	SkipCommonConfig bool
	ForceStop        bool
}

HarnessConfig describes the VM pool a functional-test package boots.

PoolName names the pool's VM instances and logging scope. BaselineTag scopes the cached baseline template overlay so pools that boot different YANET configurations never collide on one cached snapshot file. QEMUImage, when empty, defaults to the shared functional-test image resolved from the project root. The YAML fields default to the Default*/DataplaneConfig builders when left empty.

type ICMPBuilder

type ICMPBuilder struct {
	// contains filtered or unexported fields
}

func ICMP

func ICMP(opts ...ICMPOption) *ICMPBuilder

func (*ICMPBuilder) Build

type ICMPOption

type ICMPOption func(*ICMPBuilder)

func ICMPChecksum

func ICMPChecksum(checksum uint16) ICMPOption

func ICMPId

func ICMPId(id uint16) ICMPOption

func ICMPSeq

func ICMPSeq(seq uint16) ICMPOption

func ICMPType

func ICMPType(icmpType layers.ICMPv4TypeCode) ICMPOption

func ICMPTypeCode

func ICMPTypeCode(icmpType uint8, code uint8) ICMPOption

type ICMPv6Builder

type ICMPv6Builder struct {
	// contains filtered or unexported fields
}

func ICMPv6

func ICMPv6(opts ...ICMPv6Option) *ICMPv6Builder

ICMPv6 creates a generic ICMPv6 message. The concrete type/code can be provided via ICMPv6Type / ICMPv6Code options, which is useful for control messages like Router Solicitation that are not modeled as dedicated builders.

func ICMPv6DestUnreach

func ICMPv6DestUnreach(opts ...ICMPv6Option) *ICMPv6Builder

func ICMPv6EchoReply

func ICMPv6EchoReply(opts ...ICMPv6Option) *ICMPv6Builder

func ICMPv6EchoRequest

func ICMPv6EchoRequest(opts ...ICMPv6Option) *ICMPv6Builder

func ICMPv6PacketTooBig

func ICMPv6PacketTooBig(opts ...ICMPv6Option) *ICMPv6Builder

func ICMPv6ParamProblem

func ICMPv6ParamProblem(opts ...ICMPv6Option) *ICMPv6Builder

func ICMPv6TimeExceeded

func ICMPv6TimeExceeded(opts ...ICMPv6Option) *ICMPv6Builder

func (*ICMPv6Builder) Build

type ICMPv6EchoBuilder

type ICMPv6EchoBuilder struct {
	// contains filtered or unexported fields
}

ICMPv6EchoBuilder builds an ICMPv6 Echo layer

func ICMPv6Echo

func ICMPv6Echo(opts ...ICMPv6EchoOption) *ICMPv6EchoBuilder

func (*ICMPv6EchoBuilder) Build

type ICMPv6EchoOption

type ICMPv6EchoOption func(*ICMPv6EchoBuilder)

func ICMPv6EchoId

func ICMPv6EchoId(id uint16) ICMPv6EchoOption

func ICMPv6EchoSeq

func ICMPv6EchoSeq(seq uint16) ICMPv6EchoOption

type ICMPv6NeighborAdvertisementBuilder

type ICMPv6NeighborAdvertisementBuilder struct {
	// contains filtered or unexported fields
}

ICMPv6NeighborAdvertisementBuilder builds an ICMPv6 Neighbor Advertisement layer

func ICMPv6NeighborAdvertisement

func ICMPv6NeighborAdvertisement() *ICMPv6NeighborAdvertisementBuilder

func (*ICMPv6NeighborAdvertisementBuilder) Build

type ICMPv6NeighborSolicitationBuilder

type ICMPv6NeighborSolicitationBuilder struct {
	// contains filtered or unexported fields
}

ICMPv6NeighborSolicitationBuilder builds an ICMPv6 Neighbor Solicitation layer

func ICMPv6NeighborSolicitation

func ICMPv6NeighborSolicitation() *ICMPv6NeighborSolicitationBuilder

func (*ICMPv6NeighborSolicitationBuilder) Build

type ICMPv6Option

type ICMPv6Option func(*ICMPv6Builder)

func ICMPv6Checksum

func ICMPv6Checksum(checksum uint16) ICMPv6Option

func ICMPv6Code

func ICMPv6Code(code uint8) ICMPv6Option

func ICMPv6Id

func ICMPv6Id(id uint16) ICMPv6Option

func ICMPv6Seq

func ICMPv6Seq(seq uint16) ICMPv6Option

func ICMPv6Type

func ICMPv6Type(typ uint8) ICMPv6Option

ICMPv6Type sets the ICMPv6 type while preserving the current code. This is used for generic control messages (e.g. Router Solicitation) where the type comes directly from the original PCAP.

type ICMPv6RouterAdvertisementBuilder

type ICMPv6RouterAdvertisementBuilder struct {
	// contains filtered or unexported fields
}

ICMPv6RouterAdvertisementBuilder builds an ICMPv6 Router Advertisement layer

func ICMPv6RouterAdvertisement

func ICMPv6RouterAdvertisement() *ICMPv6RouterAdvertisementBuilder

func (*ICMPv6RouterAdvertisementBuilder) Build

type ICMPv6RouterSolicitationBuilder

type ICMPv6RouterSolicitationBuilder struct {
	// contains filtered or unexported fields
}

ICMPv6RouterSolicitationBuilder builds an ICMPv6 Router Solicitation layer

func ICMPv6RouterSolicitation

func ICMPv6RouterSolicitation() *ICMPv6RouterSolicitationBuilder

func (*ICMPv6RouterSolicitationBuilder) Build

type IPSecESPBuilder

type IPSecESPBuilder struct {
	// contains filtered or unexported fields
}

func IPSecESP

func IPSecESP(opts ...IPSecESPOption) *IPSecESPBuilder

func (*IPSecESPBuilder) Build

type IPSecESPOption

type IPSecESPOption func(*IPSecESPBuilder)

func ESPEncrypted

func ESPEncrypted(data []byte) IPSecESPOption

func ESPSPI

func ESPSPI(spi uint32) IPSecESPOption

func ESPSeq

func ESPSeq(seq uint32) IPSecESPOption

type IPv4Builder

type IPv4Builder struct {
	// contains filtered or unexported fields
}

func IPv4

func IPv4(opts ...IPv4Option) *IPv4Builder

IPv4 creates an IPv4 layer builder for packet construction

func (*IPv4Builder) Build

type IPv4Option

type IPv4Option func(*IPv4Builder)

func IPDst

func IPDst(ip string) IPv4Option

func IPFlags

func IPFlags(flags layers.IPv4Flag) IPv4Option

func IPFragOffset

func IPFragOffset(offset uint16) IPv4Option

func IPId

func IPId(id uint16) IPv4Option

func IPProto

func IPProto(proto layers.IPProtocol) IPv4Option

func IPSrc

func IPSrc(ip string) IPv4Option

func IPTOS

func IPTOS(tos uint8) IPv4Option

func IPTTL

func IPTTL(ttl uint8) IPv4Option

func IPv4ChecksumRaw

func IPv4ChecksumRaw(cs uint16) IPv4Option

func IPv4DummyOptions

func IPv4DummyOptions(size int) IPv4Option

func IPv4IHL

func IPv4IHL(ihl uint8) IPv4Option

func IPv4Length

func IPv4Length(length uint16) IPv4Option

func IPv4Options

func IPv4Options(opts []IPv4OptionDef) IPv4Option

IPv4Options sets IPv4 options and automatically updates IHL

func IPv4RawOptions

func IPv4RawOptions(data []byte) IPv4Option

IPv4RawOptions sets raw IPv4 options data (for non-standard options)

type IPv4OptionDef

type IPv4OptionDef struct {
	Type   uint8
	Length uint8
	Data   []byte
}

IPv4OptionDef defines a single IPv4 option

type IPv6Builder

type IPv6Builder struct {
	// contains filtered or unexported fields
}

func IPv6

func IPv6(opts ...IPv6Option) *IPv6Builder

func (*IPv6Builder) Build

type IPv6ExtHdrDestOptBuilder

type IPv6ExtHdrDestOptBuilder struct {
	// contains filtered or unexported fields
}

IPv6ExtHdrDestOptBuilder builds IPv6 Destination Options extension header

func IPv6ExtHdrDestOpt

func IPv6ExtHdrDestOpt(opts ...IPv6ExtHdrDestOptOption) *IPv6ExtHdrDestOptBuilder

IPv6ExtHdrDestOpt creates a new IPv6 Destination Options extension header builder

func (*IPv6ExtHdrDestOptBuilder) Build

Build constructs the IPv6 Destination Options extension header

type IPv6ExtHdrDestOptOption

type IPv6ExtHdrDestOptOption func(*IPv6ExtHdrDestOptBuilder)

IPv6ExtHdrDestOptOption is an option for IPv6ExtHdrDestOpt

func IPv6DestOptAddOption

func IPv6DestOptAddOption(optType uint8, data []byte) IPv6ExtHdrDestOptOption

IPv6DestOptAddOption adds an option to the destination options header

func IPv6DestOptNextHeader

func IPv6DestOptNextHeader(nh layers.IPProtocol) IPv6ExtHdrDestOptOption

IPv6DestOptNextHeader sets the next header field (for compatibility, but not used in gopacket) The next header is automatically determined during serialization

type IPv6FragmentBuilder

type IPv6FragmentBuilder struct {
	// contains filtered or unexported fields
}

func IPv6ExtHdrFragment

func IPv6ExtHdrFragment(opts ...IPv6FragmentOption) *IPv6FragmentBuilder

func (*IPv6FragmentBuilder) Build

type IPv6FragmentOption

type IPv6FragmentOption func(*IPv6FragmentBuilder)

func IPv6FragId

func IPv6FragId(id uint32) IPv6FragmentOption

func IPv6FragM

func IPv6FragM(m bool) IPv6FragmentOption

func IPv6FragNextHeader

func IPv6FragNextHeader(nh layers.IPProtocol) IPv6FragmentOption

func IPv6FragOffset

func IPv6FragOffset(offset uint16) IPv6FragmentOption

type IPv6Option

type IPv6Option func(*IPv6Builder)

func IPv6Dst

func IPv6Dst(ip string) IPv6Option

func IPv6FlowLabel

func IPv6FlowLabel(fl uint32) IPv6Option

func IPv6HopLimit

func IPv6HopLimit(hl uint8) IPv6Option

func IPv6NextHeader

func IPv6NextHeader(nh layers.IPProtocol) IPv6Option

func IPv6PayloadLength

func IPv6PayloadLength(plen uint16) IPv6Option

func IPv6Src

func IPv6Src(ip string) IPv6Option

func IPv6TrafficClass

func IPv6TrafficClass(tc uint8) IPv6Option

type LayerBuilder

type LayerBuilder interface {
	Build() gopacket.SerializableLayer
}

LayerBuilder interface for all layer builders

type MPLSBuilder

type MPLSBuilder struct {
	// contains filtered or unexported fields
}

MPLSBuilder builds MPLS layer

func MPLS

func MPLS(opts ...MPLSOption) *MPLSBuilder

MPLS creates a new MPLS layer builder

func (*MPLSBuilder) Build

Build constructs the MPLS layer

type MPLSOption

type MPLSOption func(*MPLSBuilder)

MPLSOption is an option for MPLS

func MPLSLabel

func MPLSLabel(label uint32) MPLSOption

MPLSLabel sets the MPLS label

func MPLSStackBit

func MPLSStackBit(stackBit bool) MPLSOption

MPLSStackBit sets the bottom-of-stack bit

func MPLSTTL

func MPLSTTL(ttl uint8) MPLSOption

MPLSTTL sets the MPLS TTL

func MPLSTrafficClass

func MPLSTrafficClass(tc uint8) MPLSOption

MPLSTrafficClass sets the traffic class (experimental bits)

type PacketInfo

type PacketInfo struct {
	// Ethernet layer addressing information
	SrcMAC net.HardwareAddr // Source MAC address from Ethernet header
	DstMAC net.HardwareAddr // Destination MAC address from Ethernet header

	// IP layer information (supports both IPv4 and IPv6)
	IsIPv4     bool              // True if packet contains IPv4 header
	IsIPv6     bool              // True if packet contains IPv6 header
	SrcIP      net.IP            // Source IP address (IPv4 or IPv6)
	DstIP      net.IP            // Destination IP address (IPv4 or IPv6)
	Protocol   layers.IPProtocol // IPv4 protocol field
	NextHeader layers.IPProtocol // IPv6 next header field

	// Transport layer information
	SrcPort uint16 // Source port number (TCP/UDP)
	DstPort uint16 // Destination port number (TCP/UDP)

	// Tunnel detection and analysis
	IsTunneled  bool        // True if packet is tunneled/encapsulated
	TunnelType  string      // Tunnel type: "ip4in4", "ip6in4", "ip4in6", "ip6in6", "gre"
	InnerPacket *PacketInfo // Parsed information about the inner/encapsulated packet

	// Raw packet data
	RawData []byte // Complete raw packet bytes including all headers
	Payload []byte // Application layer payload data
}

PacketInfo contains comprehensive parsed information about a network packet, including all protocol layers from Ethernet to application data. It supports complex scenarios such as tunneled packets, fragmentation, and various encapsulation protocols commonly used in network testing.

The structure provides detailed information about:

  • Ethernet layer addressing (MAC addresses)
  • IP layer information (IPv4/IPv6 addressing and protocols)
  • Transport layer details (TCP/UDP port information)
  • Tunnel detection and inner packet parsing
  • Raw packet data and payload extraction

func (*PacketInfo) GetTransportProtocol

func (info *PacketInfo) GetTransportProtocol() (layers.IPProtocol, bool)

GetTransportProtocol returns the transport layer protocol (TCP/UDP) from the packet. For tunneled packets, it returns the protocol from the inner packet. Returns the IPProtocol value and a boolean indicating if a valid transport protocol was found.

func (*PacketInfo) String

func (info *PacketInfo) String() string

String returns a human-readable string representation of the packet information, including all relevant protocol details, addressing information, and tunnel status. This method is useful for debugging, logging, and test result presentation.

The string representation includes:

  • Source and destination IP addresses
  • IP version information (IPv4/IPv6) with protocol details
  • Transport layer port information (if available)
  • Tunnel information and inner packet details (if tunneled)

Returns:

  • string: Formatted packet information suitable for display and logging

Example output:

"Packet: 192.168.1.1 -> 10.0.0.1 (IPv4, proto=4), tunnel=ip4in4, inner=172.16.1.1->172.16.1.2"

type PacketParser

type PacketParser struct{}

PacketParser provides comprehensive packet parsing and verification functionality for network testing scenarios. It supports complex packet analysis including tunnel detection, protocol verification, and NAT64 translation validation.

The parser handles various networking protocols and encapsulation methods commonly encountered in YANET testing environments.

func NewPacketParser

func NewPacketParser() *PacketParser

NewPacketParser creates a new packet parser instance ready for packet analysis. The parser is stateless and can be safely used concurrently across multiple goroutines for parallel packet processing.

Returns:

  • *PacketParser: A new packet parser instance

func (*PacketParser) ParsePacket

func (p *PacketParser) ParsePacket(data []byte) (*PacketInfo, error)

ParsePacket parses raw packet data and extracts comprehensive protocol information into a structured PacketInfo object. The method handles various networking protocols and automatically detects tunneled/encapsulated packets.

The parsing process includes:

  • Ethernet frame validation and padding to minimum size
  • Multi-layer protocol parsing (Ethernet, IP, Transport)
  • Tunnel detection and inner packet analysis
  • Payload extraction and error handling

Parameters:

  • data: Raw packet bytes to parse (minimum 60 bytes after padding)

Returns:

  • *PacketInfo: Structured packet information with all parsed layers
  • error: An error if packet parsing fails or data is malformed

Example:

parser := NewPacketParser()
info, err := parser.ParsePacket(rawPacketData)
if err != nil {
    log.Fatalf("Packet parsing failed: %v", err)
}
fmt.Printf("Parsed packet: %s", info.String())

func (*PacketParser) VerifyDecapsulation

func (p *PacketParser) VerifyDecapsulation(originalPacket, processedPacket *PacketInfo) error

VerifyDecapsulation validates that a tunneled packet has been properly decapsulated by comparing the processed packet against the original inner packet. This verification method is essential for testing tunnel processing functionality.

The verification process includes:

  • Confirming the original packet was tunneled
  • Validating inner packet information exists
  • Comparing IP version consistency (IPv4/IPv6)
  • Verifying source and destination IP address preservation
  • Ensuring tunnel headers have been properly removed

Parameters:

  • originalPacket: The original tunneled packet before processing
  • processedPacket: The packet after decapsulation processing

Returns:

  • error: An error if decapsulation verification fails or packets don't match

Example:

err := parser.VerifyDecapsulation(tunneled, decapsulated)
if err != nil {
    log.Fatalf("Decapsulation verification failed: %v", err)
}

func (*PacketParser) VerifyNAT64Translation

func (p *PacketParser) VerifyNAT64Translation(originalPacket, translatedPacket *PacketInfo, nat64Prefix string) error

VerifyNAT64Translation validates NAT64 protocol translation between IPv4 and IPv6 packets, ensuring proper address mapping and prefix usage. This method is crucial for testing NAT64 functionality in dual-stack network environments.

The verification process includes:

  • IPv4-to-IPv6 translation validation with prefix checking
  • IPv6-to-IPv4 translation validation with embedded address extraction
  • NAT64 prefix compliance verification (typically 64:ff9b::/96)
  • Embedded IPv4 address consistency checking

Supported translation directions:

  • IPv4 → IPv6: Verifies IPv6 address is in NAT64 prefix with embedded IPv4
  • IPv6 → IPv4: Verifies original IPv6 was in prefix and extracts embedded IPv4

Parameters:

  • originalPacket: The packet before NAT64 translation
  • translatedPacket: The packet after NAT64 translation
  • nat64Prefix: NAT64 prefix in CIDR notation (e.g., "64:ff9b::/96")

Returns:

  • error: An error if NAT64 translation verification fails or addresses don't match

Example:

err := parser.VerifyNAT64Translation(ipv4Pkt, ipv6Pkt, "64:ff9b::/96")
if err != nil {
    log.Fatalf("NAT64 translation verification failed: %v", err)
}

type QEMUManager

type QEMUManager struct {
	Name        string
	ImagePath   string
	WorkDir     string
	Command     *exec.Cmd
	LogsDir     string
	ConfigDir   string
	BuildDir    string
	TargetDir   string
	ProjectDir  string
	SerialPath  string
	MonitorPath string
	SocketPaths []string

	Ninepmounted atomic.Bool

	// TemplateOverlay is an optional path to a qcow2 overlay that already
	// contains a reusable VM snapshot. When set, Start() copies it instead
	// of creating a blank overlay, then boots with -loadvm TemplateSnapshotName.
	TemplateOverlay string
	// TemplateSnapshotName is the snapshot name loaded from TemplateOverlay.
	// When empty, Start() falls back to BootedSnapshotName.
	TemplateSnapshotName string
	// contains filtered or unexported fields
}

QEMUManager handles the complete lifecycle and operations of QEMU virtual machines for YANET functional testing. It provides comprehensive VM management including startup, networking configuration, filesystem sharing, and graceful shutdown.

The manager supports:

  • QEMU VM lifecycle management with proper resource cleanup
  • Unix socket-based networking for packet injection and capture
  • 9P filesystem sharing for host-VM file exchange
  • Serial console and monitor interface access
  • VM readiness detection and synchronization
  • Parallel test execution with unique instance isolation

All operations are thread-safe and support concurrent access patterns required for comprehensive network testing scenarios.

func NewQEMUManager

func NewQEMUManager(name string, imagePath string, logger *zap.SugaredLogger) (*QEMUManager, error)

NewQEMUManager creates and initializes a new QEMU manager instance for virtual machine testing. The manager sets up all necessary directories, generates unique instance identifiers for parallel execution, and configures filesystem sharing paths for host-VM communication.

The initialization process includes:

  • Unique instance ID generation for parallel test isolation
  • Working directory creation in system temporary space
  • Project root detection for build and target directory sharing
  • Socket path configuration for VM networking and console access

Parameters:

  • name: Name of this manager
  • imagePath: Path to the QEMU disk image file (must exist and be accessible)
  • logger: Structured logger for debugging and monitoring VM operations

Returns:

  • *QEMUManager: Configured QEMU manager ready for VM startup
  • error: An error if project root detection fails or paths are invalid

Example:

manager, err := NewQEMUManager("main", "/path/to/vm-image.qcow2", logger)
if err != nil {
    log.Fatalf("Failed to create QEMU manager: %v", err)
}

func (*QEMUManager) AbortSerial

func (q *QEMUManager) AbortSerial()

AbortSerial stops the serial reader and closes the serial connection, causing in-flight ExecuteCommand calls to fail immediately. The caller must reconnect (via reset/up) before issuing further guest commands.

func (*QEMUManager) AttachSerial

func (q *QEMUManager) AttachSerial() (net.Conn, func() error, error)

AttachSerial gives a caller exclusive access to the serial console.

The returned release function restores the framework's serial reader and waits for a fresh prompt before another framed command can run.

func (*QEMUManager) EnableSSHForward

func (q *QEMUManager) EnableSSHForward()

EnableSSHForward configures a loopback SSH forward for this VM.

func (*QEMUManager) ForceStop

func (q *QEMUManager) ForceStop()

ForceStop makes Stop terminate this VM even when the test keep-alive mode is set.

func (*QEMUManager) GetStdin

func (q *QEMUManager) GetStdin() io.WriteCloser

GetStdin returns the stdin pipe for the QEMU process

func (*QEMUManager) IsVMReady

func (q *QEMUManager) IsVMReady() bool

IsVMReady returns the current readiness state of the virtual machine in a thread-safe manner. This method can be called from multiple goroutines without synchronization concerns.

VM readiness indicates that the virtual machine has completed its boot process and is ready to accept and execute commands through the serial console.

Returns:

  • bool: True if the VM is ready for command execution, false otherwise

Example:

if manager.IsVMReady() {
    // Safe to execute commands
    output, err := cli.ExecuteCommand("ls -la")
}

func (*QEMUManager) ReconnectSerial

func (q *QEMUManager) ReconnectSerial() error

ReconnectSerial closes the current serial connection and opens a new one. The caller is responsible for resetting readySignal and launching a new readSerial goroutine after this returns.

func (*QEMUManager) RestartSerial

func (q *QEMUManager) RestartSerial() error

RestartSerial reconnects the serial console and restarts the reader goroutine after an AbortSerial call. Use this to restore command execution capability after an abort.

func (*QEMUManager) RestoreBooted

func (q *QEMUManager) RestoreBooted() error

RestoreBooted restores the VM to the "booted" snapshot without going through the full framework-level RestoreAndReconnect. It handles the monitor loadvm, serial reconnect, and ready wait at the QEMUManager level.

Callers are responsible for unmounting 9P before calling and remounting after.

func (*QEMUManager) RestoreSnapshot

func (q *QEMUManager) RestoreSnapshot(name string) error

RestoreSnapshot restores the VM to a previously saved snapshot. After restore, the serial console connection is broken (the guest-side state reverts) and must be re-established via ReconnectSerial.

The monitor connection itself survives because it is external to the guest state.

func (*QEMUManager) SSHPort

func (q *QEMUManager) SSHPort() int

SSHPort returns the loopback port forwarded to the guest SSH server.

func (*QEMUManager) SaveBootedOverlay

func (q *QEMUManager) SaveBootedOverlay() (string, error)

SaveBootedOverlay saves the "booted" snapshot to the VM's current overlay and returns the overlay file path. The caller can copy this path to a cache location and use it as TemplateOverlay for other VMs.

func (*QEMUManager) SaveSnapshot

func (q *QEMUManager) SaveSnapshot(name string) error

SaveSnapshot creates a named QEMU VM snapshot via the monitor. The snapshot captures the full VM state (CPU, RAM, devices) and can be restored later with RestoreSnapshot. This is the key primitive for per-test state isolation.

func (*QEMUManager) SaveSnapshotOverlay

func (q *QEMUManager) SaveSnapshotOverlay(name string) (string, error)

SaveSnapshotOverlay saves the named snapshot to the VM's current overlay and returns the overlay file path. The caller can copy this path to a cache location and use it as TemplateOverlay for other VMs.

The 9P shares must be unmounted before calling (savevm blocks when VirtFS mounts are active).

func (*QEMUManager) SendMonitorCommand

func (q *QEMUManager) SendMonitorCommand(cmd string) (string, error)

SendMonitorCommand sends a command to the QEMU monitor (HMP) and returns the response. The monitor socket must already be connected via Start(). Commands are terminated with a newline; the method waits for the next "(qemu) " prompt to determine when the response is complete.

func (*QEMUManager) Start

func (q *QEMUManager) Start() (bool, error)

Start launches a QEMU virtual machine. When TemplateOverlay is set the overlay is copied from it and QEMU starts with -loadvm for the requested TemplateSnapshotName, skipping the slow cold boot path.

Returns (true, nil) when booted from snapshot, (false, nil) when doing a full cold boot.

func (*QEMUManager) Stop

func (q *QEMUManager) Stop() error

Stop performs graceful termination of the QEMU virtual machine and comprehensive cleanup of all associated resources. This method ensures proper resource deallocation and prevents resource leaks in testing environments.

The cleanup process includes:

  • Graceful closure of monitor and serial console connections
  • QEMU process termination with proper signal handling
  • Working directory and temporary file cleanup
  • Unix socket file removal from filesystem
  • Error collection and reporting for failed cleanup operations

Multiple cleanup errors are collected and returned as a combined error to provide comprehensive information about any cleanup failures.

Returns:

  • error: A combined error if any cleanup operations fail, or nil if successful

Example:

if err := manager.Stop(); err != nil {
    log.Errorf("VM cleanup encountered errors: %v", err)
}

func (*QEMUManager) WaitForReady

func (q *QEMUManager) WaitForReady(timeout time.Duration) error

WaitForReady blocks until the virtual machine becomes ready for command execution or the specified timeout expires. This method provides synchronous waiting for VM readiness with proper timeout handling.

The method first checks if the VM is already ready to avoid unnecessary waiting. If not ready, it waits for the readiness signal from the background monitoring goroutine.

Parameters:

  • timeout: Maximum time to wait for VM readiness

Returns:

  • error: An error if the timeout expires before VM becomes ready, or nil if ready

Example:

if err := manager.WaitForReady(60 * time.Second); err != nil {
    log.Fatalf("VM failed to become ready: %v", err)
}

type RawBuilder

type RawBuilder struct {
	// contains filtered or unexported fields
}

func Raw

func Raw(data []byte) *RawBuilder

func (*RawBuilder) Build

type ReplyBudget

type ReplyBudget struct {
	// contains filtered or unexported fields
}

ReplyBudget bounds how many reads one subtest will wait out for replies that never arrive.

A caller creates one ReplyBudget per subtest and passes it to every SendAndReceivePacket call in that subtest. A reply that arrives costs the pool nothing, because only a timed-out read debits it. Once drained, later reads still wait at exhaustedReadTimeout rather than skipping outright, so a reply that arrives promptly is still collected. The debit is a flat per-read cap, not an elapsed-time one, so a single read can still exceed it.

func NewReplyBudget

func NewReplyBudget() *ReplyBudget

NewReplyBudget returns a ReplyBudget with a fresh pool for one subtest.

type SocketClient

type SocketClient struct {
	// contains filtered or unexported fields
}

SocketClient handles bidirectional communication with QEMU virtual machine network interfaces through socket connections. It supports both TCP and Unix socket connections for packet injection and capture in testing scenarios.

The client provides:

  • Reliable packet transmission with length prefixing
  • Packet reception with filtering and timeout handling
  • Connection management with automatic retry logic
  • MAC address-based packet filtering for test isolation
  • Configurable timeouts and logging for debugging

All network operations include proper error handling and timeout management to ensure robust testing in various network conditions.

func NewSocketClient

func NewSocketClient(socketPath string, opts ...SocketClientOption) (*SocketClient, error)

NewSocketClient creates a new socket client configured for Unix socket connections to QEMU network interfaces. This method provides the primary interface for packet injection and capture through Unix domain sockets.

Unix socket connections offer better performance and lower latency compared to TCP sockets, making them ideal for high-throughput network testing scenarios.

Parameters:

  • socketPath: Path to the Unix socket file created by QEMU
  • opts: Optional functional options for client customization

Returns:

  • *SocketClient: Configured socket client ready for Unix socket connections
  • error: An error if the socket path is empty or options cannot be applied

Example:

client, err := NewSocketClient("/tmp/qemu-socket.sock", SocketClientWithLog(logger))
if err != nil {
    log.Fatalf("Failed to create Unix socket client: %v", err)
}

func NewSocketClientTCP

func NewSocketClientTCP(port int, opts ...SocketClientOption) (*SocketClient, error)

NewSocketClientTCP creates a new socket client configured for TCP connections to QEMU network interfaces. This method is used when QEMU is configured with TCP socket networking for packet injection and capture.

The client is initialized with default settings and can be customized using functional options for timeout configuration and logging setup.

Parameters:

  • port: TCP port number for QEMU socket connection (1-65535)
  • opts: Optional functional options for client customization

Returns:

  • *SocketClient: Configured socket client ready for TCP connections
  • error: An error if the port number is invalid or options cannot be applied

Example:

client, err := NewSocketClientTCP(8080, WithTimeout(10*time.Second))
if err != nil {
    log.Fatalf("Failed to create TCP socket client: %v", err)
}

func (*SocketClient) Close

func (sc *SocketClient) Close() error

Close gracefully terminates the socket connection and releases associated network resources. This method should be called when the socket client is no longer needed to prevent resource leaks.

The method safely handles cases where no connection is established and provides proper error reporting for connection closure failures.

Returns:

  • error: An error if connection closure fails, or nil if successful or no connection exists

Example:

defer func() {
    if err := client.Close(); err != nil {
        log.Errorf("Failed to close socket connection: %v", err)
    }
}()

func (*SocketClient) Connect

func (sc *SocketClient) Connect() error

Connect establishes a network connection to the QEMU socket interface with automatic retry logic and proper error handling. The method supports both TCP and Unix socket connections based on the client configuration.

The connection process includes:

  • Automatic detection of connection type (TCP vs Unix socket)
  • Retry logic with exponential backoff for handling timing issues
  • Proper error reporting and logging for debugging
  • Connection state management to prevent duplicate connections

The method attempts connection up to 10 times with 2-second intervals to accommodate QEMU startup timing and network interface availability.

Returns:

  • error: An error if connection cannot be established after all retry attempts

Example:

if err := client.Connect(); err != nil {
    log.Fatalf("Failed to connect to QEMU socket: %v", err)
}

func (*SocketClient) GetSocketPort

func (sc *SocketClient) GetSocketPort() int

GetSocketPort returns the TCP port number configured for this socket client. This method is useful for debugging and logging purposes, particularly when working with multiple socket clients on different ports.

Returns:

  • int: TCP port number, or 0 if the client is configured for Unix sockets

Example:

port := client.GetSocketPort()
if port > 0 {
    log.Infof("Client configured for TCP port %d", port)
} else {
    log.Info("Client configured for Unix socket")
}

func (*SocketClient) ReceiveAllPackets

func (sc *SocketClient) ReceiveAllPackets(timeout time.Duration, dumpPath string) ([][]byte, error)

ReceiveAllPackets receives all packets available on the socket within the timeout.

func (*SocketClient) ReceiveAllPacketsUnfiltered

func (sc *SocketClient) ReceiveAllPacketsUnfiltered(timeout time.Duration, dumpPath string) ([][]byte, error)

ReceiveAllPacketsUnfiltered receives every packet observed on the socket during a fixed window of timeout, without the destination-MAC filtering ReceiveAllPackets applies.

Solicited responses to injected traffic always carry the host's simulated MAC as destination, which is what that filter matches. Traffic the dataplane emits on its own initiative often addresses a multicast destination instead and would otherwise be silently dropped, so callers that need to observe unsolicited traffic must use this method.

Unlike ReceiveAllPackets, which stops as soon as the link falls idle for timeout, this method always reads for the entire window: a source that emits periodic unsolicited traffic more often than timeout never falls idle, so an idle-based cutoff would never return.

func (*SocketClient) ReceivePacket

func (sc *SocketClient) ReceivePacket(timeout time.Duration, dumpPath string) ([]byte, error)

ReceivePacket captures a network packet from the QEMU socket connection with MAC address filtering and timeout handling. The method implements the QEMU socket protocol by reading length-prefixed packets and filtering based on destination MAC address for test isolation.

The reception process includes:

  • Connection state validation and timeout configuration
  • Length prefix parsing from network byte order
  • Packet size validation to prevent buffer overflows
  • Complete packet data reception
  • MAC address-based filtering for test packet isolation
  • Automatic packet parsing and validation
  • Optional dumping to file if dumpFilePaths is provided

The method continuously reads packets until it finds one with the correct destination MAC address matching the test framework's source MAC, ensuring proper packet isolation in multi-test scenarios.

Parameters:

  • timeout: Maximum time to wait for packet reception
  • dumpPath: Path to dump file for recording socket data (empty string to skip dumping)

Returns:

  • []byte: Raw packet data with correct destination MAC address
  • error: An error if connection fails, timeout occurs, or no matching packet is received

Example:

packet, err := client.ReceivePacket(5 * time.Second, "/path/to/dump.file")
if err != nil {
    log.Fatalf("Packet reception failed: %v", err)
}
fmt.Printf("Received packet: %x", packet)

func (*SocketClient) ResetConnection

func (sc *SocketClient) ResetConnection() error

ResetConnection closes the existing connection and creates a new one. This ensures a clean stream with no buffered data from previous operations. This method is used for test isolation to prevent packet leakage between tests.

The method:

  • Closes any existing connection (discarding buffered data)
  • Creates a new connection to the same socket
  • Returns an error if reconnection fails

Returns:

  • error: An error if reconnection fails, or nil if successful

Example:

if err := client.ResetConnection(); err != nil {
    log.Fatalf("Failed to reset connection: %v", err)
}

func (*SocketClient) SendAndReceivePacket

func (sc *SocketClient) SendAndReceivePacket(budget *ReplyBudget, packet []byte, dumpPath string) ([]byte, error)

SendAndReceivePacket transmits packet and waits for a single reply, returning the raw reply bytes.

The wait is not retried on timeout: a late reply is far more likely than a lost one under a starved CI runner, and resending risks a duplicate packet through stateful modules. budget must be shared by every call in one subtest — see ReplyBudget for what that buys when replies are missing by construction. A missing reply returns a nil slice and a nil error, not an error, and surfaces later as a packet-count mismatch.

Parameters:

  • budget: Per-subtest pool debited only when a read times out
  • packet: Raw packet bytes to transmit through the socket
  • dumpPath: Path to dump file for recording socket data (empty string to skip dumping)

Returns:

  • []byte: Raw reply bytes, or nil if no reply arrived
  • error: An error if the packet could not be sent, or the read failed for a reason other than a timeout

func (*SocketClient) SendPacket

func (sc *SocketClient) SendPacket(packet []byte, dumpPath string) error

SendPacket transmits a raw network packet through the QEMU socket connection with proper length prefixing and timeout handling. The method implements the QEMU socket protocol by prefixing each packet with its length in network byte order.

The transmission process includes:

  • Connection state validation
  • Write timeout configuration for reliable transmission
  • Length prefix encoding in big-endian format
  • Complete packet data transmission
  • Comprehensive error handling and logging
  • Optional dumping to file if dumpFilePaths is provided

The packet format follows QEMU socket networking protocol:

[4-byte length][packet data]

Parameters:

  • packet: Raw packet bytes to transmit through the socket
  • dumpPath: Path to dump file for recording socket data (empty string to skip dumping)

Returns:

  • error: An error if the connection is not established, timeout occurs, or transmission fails

Example:

packetData := []byte{0x52, 0x54, 0x00, 0x6b, 0xff, 0xa1, ...} // Ethernet frame
if err := client.SendPacket(packetData, "/path/to/dump.file"); err != nil {
    log.Fatalf("Packet transmission failed: %v", err)
}

func (*SocketClient) SendPackets

func (sc *SocketClient) SendPackets(packets [][]byte, dumpPath string) error

func (*SocketClient) WithLog

func (sc *SocketClient) WithLog(log *zap.SugaredLogger) *SocketClient

WithLog creates a new SocketClient instance with a different logger while sharing the same underlying connection (inner state). This allows each test to have its own logging context while sharing the socket connection.

Parameters:

  • log: Logger to use for this client instance

Returns:

  • *SocketClient: A new socket client instance with the specified logger

Example:

namedClient := client.WithLog(logger.Named("test1"))

type SocketClientOption

type SocketClientOption func(*SocketClient) error

SocketClientOption defines functional options for configuring SocketClient instances. This pattern allows flexible initialization with optional parameters while maintaining clean API design and backward compatibility.

func WithTimeout

func WithTimeout(timeout time.Duration) SocketClientOption

WithTimeout configures the default timeout for socket read and write operations. This timeout applies to both packet transmission and reception operations, providing consistent behavior across all network interactions.

Parameters:

  • timeout: Duration for socket operation timeouts (must be positive)

Returns:

  • SocketClientOption: Functional option that sets the timeout
  • error: An error if the timeout value is invalid (zero or negative)

Example:

client, err := NewSocketClient(path, WithTimeout(5*time.Second))

type TCPBuilder

type TCPBuilder struct {
	// contains filtered or unexported fields
}

func TCP

func TCP(opts ...TCPOption) *TCPBuilder

func (*TCPBuilder) Build

type TCPOption

type TCPOption func(*TCPBuilder)

func TCPAck

func TCPAck(ack uint32) TCPOption

func TCPChecksumRaw

func TCPChecksumRaw(cs uint16) TCPOption

func TCPDataOffset

func TCPDataOffset(offset uint8) TCPOption

func TCPDport

func TCPDport(port uint16) TCPOption

func TCPDummyOptions

func TCPDummyOptions(size int) TCPOption

func TCPFlags

func TCPFlags(flags string) TCPOption

func TCPOptions

func TCPOptions(opts []TCPOptionDef) TCPOption

TCPOptions sets TCP options and automatically updates DataOffset

func TCPSeq

func TCPSeq(seq uint32) TCPOption

func TCPSport

func TCPSport(port uint16) TCPOption

func TCPUrgent

func TCPUrgent(urg uint16) TCPOption

func TCPWindow

func TCPWindow(win uint16) TCPOption

type TCPOptionDef

type TCPOptionDef struct {
	Kind   layers.TCPOptionKind
	Length uint8
	Data   []byte
}

TCPOptionDef defines a single TCP option

type TCPPacketOpts

type TCPPacketOpts struct {
	SrcMAC  string // default: framework.SrcMAC
	DstMAC  string // default: framework.DstMAC
	SrcPort uint16 // default: 12345
	DstPort uint16 // default: 80
	SYN     bool   // default: false
	ACK     bool   // default: true
	PSH     bool   // default: true
	Seq     uint32 // default: 1
	Ack     uint32 // default: 1
	Window  uint16 // default: 1024
}

TCPPacketOpts configures optional fields for CreateTCPIPv4Packet and CreateTCPIPv6Packet. Zero values use sensible defaults matching the most common test pattern.

type TestFramework

type TestFramework struct {
	PacketParser *PacketParser

	Paths GuestPaths // Guest-side filesystem paths (9P or local tmpfs)
	// contains filtered or unexported fields
}

F represents the main test framework structure for YANET functional testing. It orchestrates QEMU virtual machine management, CLI command execution, packet processing, and network socket communication to provide a comprehensive testing environment.

The framework manages:

  • QEMU virtual machine lifecycle and networking
  • CLI command execution within the VM
  • Packet parsing and analysis capabilities
  • Socket-based network communication with VM interfaces
  • Test context via optional *testing.T field

All operations are thread-safe through internal synchronization mechanisms. This type has private fields and cannot be constructed directly - use Global() and ForTest(t).

func (*TestFramework) AbortGuestSerial

func (f *TestFramework) AbortGuestSerial()

AbortGuestSerial closes the guest serial connection so in-flight ExecuteCommand calls fail. The caller must reconnect before issuing further guest commands.

func (*TestFramework) AdoptRunningConfig

func (f *TestFramework) AdoptRunningConfig(dataplaneConfig string, controlplaneConfig string)

AdoptRunningConfig records the configuration to restart the guest with.

It starts nothing. A VM restored from a baseline snapshot boots with YANET already running, so the framework driving it never called StartYANET and therefore holds no configuration for RestartYANET to restart from. Call this once the caller knows what configuration the running guest was baked with, so a later in-place restart has something to fall back to.

func (*TestFramework) AttachSerial

func (f *TestFramework) AttachSerial() (net.Conn, func() error, error)

AttachSerial gives a caller exclusive access to the guest serial console.

func (*TestFramework) CommonConfigCommands

func (f *TestFramework) CommonConfigCommands() []string

CommonConfigCommands returns the shell commands that configure the baseline YANET state (kni0, forwarding, route FIB, pipelines, devices). Paths are resolved from f.Paths so the commands work with both 9P and local tmpfs layouts.

func (*TestFramework) CreateConfigFile

func (f *TestFramework) CreateConfigFile(name string, config string) error

func (*TestFramework) CreateForwardConfig

func (f *TestFramework) CreateForwardConfig(config string) error

CreateForwardConfig writes forward.yaml to the path referenced by f.Paths.ForwardYAML. In 9P mode this writes to the host filesystem; in local mode it writes via serial console to the guest tmpfs.

func (*TestFramework) ExecuteCommand

func (f *TestFramework) ExecuteCommand(command string) (string, error)

ExecuteCommand executes a single CLI command within the QEMU virtual machine via the serial console interface. This is a proxy method that delegates to the underlying CLI manager.

For test-specific frameworks (created via ForTest), commands are logged with the test's unique log ID for easy debugging.

Parameters:

  • command: The shell command to execute in the virtual machine

Returns:

  • string: The cleaned command output (stdout/stderr combined)
  • error: An error if the VM is not ready, command fails, or timeout occurs

Example:

fw := globalFramework.ForTest(t)
fw.Run("ExecuteCommand", func(fw *TestFramework, t *testing.T) {
    output, err := fw.ExecuteCommand("ls -la /etc")
    require.NoError(t, err)
    t.Logf("Output: %s", output)
})

func (*TestFramework) ExecuteCommandWithTimeout

func (f *TestFramework) ExecuteCommandWithTimeout(command string, timeout time.Duration) (string, error)

ExecuteCommandWithTimeout executes a single CLI command with a custom timeout. Use this for operations that may take longer than the default 30s, such as copying large binaries on slow emulated VMs.

func (*TestFramework) ExecuteCommands

func (f *TestFramework) ExecuteCommands(commands ...string) ([]string, error)

ExecuteCommands executes multiple CLI commands sequentially within the QEMU virtual machine. This is a proxy method that delegates to the underlying CLI manager.

Each command is executed in order, and execution stops at the first command that returns an error.

Parameters:

  • commands: Variable number of shell commands to execute sequentially

Returns:

  • []string: Slice of command outputs in execution order (may be partial if error occurs)
  • error: An error from the first failed command, or nil if all commands succeed

Example:

fw := globalFramework.ForTest(t)
fw.Run("ExecuteCommands", func(fw *TestFramework, t *testing.T) {
    outputs, err := fw.ExecuteCommands(
        "mkdir -p /tmp/test",
        "echo 'hello' > /tmp/test/file.txt",
        "cat /tmp/test/file.txt",
    )
    require.NoError(t, err)
    t.Logf("Outputs: %v", outputs)
})

func (*TestFramework) ExecuteCommandsSeparately

func (f *TestFramework) ExecuteCommandsSeparately(commands ...string) ([]string, []error)

ExecuteCommandsSeparately runs every command to completion regardless of individual failures, pairing each output with its own error.

func (*TestFramework) ExportCurrentOverlay

func (f *TestFramework) ExportCurrentOverlay(dst string) error

ExportCurrentOverlay copies the VM's current qcow2 overlay to dst.

A successful export leaves the VM paused. Callers must stop it or explicitly resume it before further use. This is used to cache prepared template overlays and start future pool VMs from the same snapshot source.

func (*TestFramework) ForTest

func (f *TestFramework) ForTest(t *testing.T) *TestFramework

ForTest binds an already constructed framework instance to a specific test. This is used by pooled VMs, where tests acquire a shared base framework from VMPool and then need a test-scoped copy with the proper logger and *testing.T.

func (*TestFramework) GetSocketClient

func (f *TestFramework) GetSocketClient(ifaceIndex int) (*SocketClient, error)

GetSocketClient retrieves or creates a socket client for the specified network interface. The method implements caching to reuse existing connections and ensures thread-safe access to the socket client pool.

For test-specific frameworks (created via ForTest), the socket client is returned with a named logger using the test's log ID.

The method handles:

  • Interface index validation against available QEMU socket paths
  • Thread-safe access to the socket client cache
  • Automatic socket client creation for new interfaces
  • Client caching for performance optimization
  • Logger customization for test-specific contexts

Parameters:

  • ifaceIndex: Zero-based index of the network interface (must be < len(SocketPaths))

Returns:

  • *SocketClient: Socket client for the specified interface
  • error: An error if the interface index is invalid or client creation fails

Example:

client, err := fw.GetSocketClient(0)
if err != nil {
    log.Fatalf("Failed to get socket client: %v", err)
}
defer client.Close()

func (*TestFramework) GetSocketPaths

func (f *TestFramework) GetSocketPaths() []string

func (*TestFramework) Mount9P

func (f *TestFramework) Mount9P() error

Mount9P remounts all 9P shares inside the guest VM. Uses mount -a for speed, then verifies each mount point is present.

func (*TestFramework) PrepareLocalStorage

func (f *TestFramework) PrepareLocalStorage() error

PrepareLocalStorage copies all YANET binaries, CLI tools, configs and scripts from 9P mounts to /tmp/yanet/ inside the guest. After this call, YANET can be started from local tmpfs paths so that no process holds open fids on 9P mounts, making savevm/loadvm work cleanly.

This also switches f.Paths to LocalGuestPaths().

func (*TestFramework) ResetConnections

func (f *TestFramework) ResetConnections()

ResetConnections closes and reconnects all socket clients. This ensures a clean state after a snapshot restore, preventing stale connections from causing false heartbeat failures. Unlike draining, this approach guarantees a completely clean stream by discarding any buffered data with the old connection.

Must be called after loadvm and before any packet operations (WaitForDatapathReady, SendPacketAndCapture, etc.) because loadvm restores QEMU's internal stream-netdev state but the host-side UNIX socket connections are stale — Connect() short-circuits on the dead net.Conn and never reconnects.

Errors during reset are logged but do not cause the operation to fail.

func (*TestFramework) RestartGuestSerial

func (f *TestFramework) RestartGuestSerial() error

RestartGuestSerial reconnects the serial console and restarts the reader goroutine after an AbortGuestSerial call.

func (*TestFramework) RestartYANET

func (f *TestFramework) RestartYANET() error

func (*TestFramework) RestoreAndReconnect

func (f *TestFramework) RestoreAndReconnect(snapshot string) error

RestoreAndReconnect reverts the VM to a previously saved snapshot and re-establishes the serial console and socket connections that break when the guest state is rolled back.

Call sequence after restore:

  1. Unmount 9P shares
  2. loadvm via QEMU monitor (monitor socket survives)
  3. Reconnect serial console
  4. Wait for shell prompts
  5. Mount 9P shares
  6. Reset socket connections (close stale connections, reconnect)
  7. Wait for dataplane ready (ICMP heartbeat)

Socket connections MUST be reset before the heartbeat because loadvm restores QEMU's internal stream-netdev state but the host-side UNIX socket connections are stale. Without reset, Connect() short-circuits on the dead net.Conn and heartbeat fails silently.

func (*TestFramework) RestoreBooted

func (f *TestFramework) RestoreBooted() error

RestoreBooted restores the VM to the "booted" snapshot and re-establishes all connections. This is the primary per-test isolation primitive.

Unlike RestoreAndReconnect("baseline"), RestoreBooted does not depend on a baseline snapshot being created at test startup. It only requires that the VM pool was started from a pre-prepared booted template overlay.

func (*TestFramework) RestoreClean

func (f *TestFramework) RestoreClean(snapshot string) error

RestoreClean reverts the VM to a previously saved snapshot and re-establishes serial console and 9P mounts WITHOUT running the dataplane heartbeat check. Use this for snapshots where YANET is not yet running (e.g. "preyanet") -- StartYANET will be called separately after restore.

func (*TestFramework) Run

func (f *TestFramework) Run(name string, fn func(fw *TestFramework, t *testing.T)) bool

Run executes a subtest with the given name and function. This method wraps t.Run() and automatically creates a new TestFramework instance with the correct test name. This ensures that all framework operations within the subtest are properly tracked and logged.

The callback function receives two parameters:

  • fw: A new *TestFramework instance with the correct test name set
  • t: The *testing.T instance for the subtest

This design separates framework operations (via fw) from test assertions (via t), making the code more explicit and preventing accidental use of the wrong test context.

Parameters:

  • name: The name of the subtest
  • fn: The test function that receives fw and t

Returns:

  • bool: True if the test passed, false otherwise (same as t.Run)

Example:

func TestMyFeature(t *testing.T) {
    fw := globalFramework.ForTest(t)

    fw.Run("BasicTest", func(fw *TestFramework, t *testing.T) {
        input, output, err := fw.SendPacketAndParse(0, 0, packet, timeout)
        require.NoError(t, err)
    })

    fw.Run("AdvancedTest", func(fw *TestFramework, t *testing.T) {
        _, err := fw.ExecuteCommand("some command")
        require.NoError(t, err)
    })
}

func (*TestFramework) RunWith

func (f *TestFramework) RunWith(snapshot string, name string, fn func(fw *TestFramework, t *testing.T)) bool

RunWith is like Run but restores the named VM snapshot before executing the test function. This gives each subtest a guaranteed clean VM state matching the configuration at snapshot time.

Use Run() (without snapshot restore) for subtests that intentionally build on the state left by previous subtests. Use RunWith() when each subtest needs full isolation.

func (*TestFramework) SSHPort

func (f *TestFramework) SSHPort() int

SSHPort returns the loopback port forwarded to the guest SSH server.

func (*TestFramework) SaveSnapshot

func (f *TestFramework) SaveSnapshot(name string) error

SaveSnapshot saves a named VM snapshot that captures the full machine state (CPU, RAM, devices). Subsequent calls to RestoreAndReconnect can revert the VM to this point. This is typically called from TestMain after YANET is configured to create a "baseline" snapshot.

The method unmounts all 9P shares before savevm (to remove QEMU migration blockers) and remounts them afterward. This only works cleanly when PrepareLocalStorage() was called first so that no process holds open fids on 9P mounts.

func (*TestFramework) SaveSnapshotKeepUnmounted

func (f *TestFramework) SaveSnapshotKeepUnmounted(name string) error

SaveSnapshotKeepUnmounted saves a snapshot like SaveSnapshot but does NOT remount 9P shares afterward. Use this for baseline snapshots in pool mode: since RestoreAndReconnect will loadvm immediately, the intermediate remount would just be unmounted again before loadvm, wasting ~4s per test.

func (*TestFramework) SendPacketAndCapture

func (f *TestFramework) SendPacketAndCapture(inputIfaceIndex int, outputIfaceIndex int, packet []byte, timeout time.Duration) ([]byte, error)

SendPacketAndCapture sends a network packet through the specified input interface and captures any response from the output interface without performing packet verification or parsing. This is a low-level method for raw packet testing.

The method handles:

  • Socket client retrieval and connection management
  • Packet transmission through the input interface
  • Response capture from the output interface with timeout
  • Automatic socket connection establishment
  • Optional socket data dumping when debug mode is enabled

Parameters:

  • inputIfaceIndex: Index of the network interface to send the packet through
  • outputIfaceIndex: Index of the network interface to capture response from
  • packet: Raw packet data to transmit
  • timeout: Maximum time to wait for response capture

Returns:

  • []byte: Raw response packet data, or nil if no response received
  • error: An error if packet transmission fails or interfaces are unavailable

Example:

fw := globalFramework.ForTest(t)
fw.Run("SendPacket", func(fw *TestFramework, t *testing.T) {
    response, err := fw.SendPacketAndCapture(0, 1, packetData, 5*time.Second)
    require.NoError(t, err)
})

func (*TestFramework) SendPacketAndCaptureAll

func (f *TestFramework) SendPacketAndCaptureAll(inputIfaceIndex int, outputIfaceIndex int, packet []byte, timeout time.Duration) ([][]byte, error)

SendPacketAndCaptureAll sends a network packet and captures all response packets.

func (*TestFramework) SendPacketAndCaptureAllUnfiltered

func (f *TestFramework) SendPacketAndCaptureAllUnfiltered(inputIfaceIndex int, outputIfaceIndex int, packet []byte, timeout time.Duration) ([][]byte, error)

SendPacketAndCaptureAllUnfiltered captures every packet observed on the selected egress during timeout.

func (*TestFramework) SendPacketAndParse

func (f *TestFramework) SendPacketAndParse(inputIfaceIndex int, outputIfaceIndex int, packet []byte, timeout time.Duration) (*PacketInfo, *PacketInfo, error)

SendPacketAndParse sends a network packet, captures the response, and parses both the input and output packets into structured PacketInfo objects. This high-level method provides comprehensive packet analysis for detailed testing scenarios.

The method performs:

  • Input packet parsing and validation
  • Packet transmission through the specified interfaces
  • Response capture with timeout handling
  • Output packet parsing and analysis
  • Detailed logging of packet flow for debugging
  • Optional socket data dumping when debug mode is enabled

Parameters:

  • inputIfaceIndex: Index of the network interface to send the packet through
  • outputIfaceIndex: Index of the network interface to capture response from
  • packet: Raw packet data to transmit
  • timeout: Maximum time to wait for response capture

Returns:

  • *PacketInfo: Parsed information about the input packet
  • *PacketInfo: Parsed information about the output packet (nil if no response)
  • error: An error if packet processing, transmission, or parsing fails

Example:

fw := globalFramework.ForTest(t)
fw.Run("ParsePacket", func(fw *TestFramework, t *testing.T) {
    input, output, err := fw.SendPacketAndParse(0, 1, packetData, 5*time.Second)
    require.NoError(t, err)
    t.Logf("Sent: %s, Received: %s", input.String(), output.String())
})

func (*TestFramework) SendPacketAndParseAll

func (f *TestFramework) SendPacketAndParseAll(inputIfaceIndex int, outputIfaceIndex int, packet []byte, timeout time.Duration) ([]*PacketInfo, error)

SendPacketAndParseAll sends a network packet and captures ALL response packets.

func (*TestFramework) SendPacketsAndParseAll

func (f *TestFramework) SendPacketsAndParseAll(inputIfaceIndex int, outputIfaceIndex int, packets [][]byte, timeout time.Duration) ([]*PacketInfo, error)

func (*TestFramework) Start

func (f *TestFramework) Start() (bool, error)

Start initializes and launches the complete test environment, including the QEMU virtual machine with configured networking. This method must be called before executing any tests or VM operations.

The startup process includes:

  • QEMU virtual machine launch with socket networking
  • Network interface initialization
  • VM readiness verification

Returns:

  • error: An error if VM startup fails or networking cannot be established

Example:

if err := framework.Start(); err != nil {
    log.Fatalf("Failed to start test environment: %v", err)
}

func (*TestFramework) StartLogTailers

func (f *TestFramework) StartLogTailers() error

StartLogTailers starts background processes that tail YANET log files from local tmpfs to the 9P-mounted /mnt/logs/ directory, making logs visible on the host in real time. Must be called after YANET is running and 9P mounts are available.

func (*TestFramework) StartYANET

func (f *TestFramework) StartYANET(dataplaneConfig string, controlplaneConfig string) error

StartYANET initializes and launches the complete YANET network processing stack within the virtual machine environment. This comprehensive method handles all aspects of YANET deployment including configuration, kernel module loading, network interface binding, and service startup.

The startup process includes:

  • Configuration file creation and validation
  • YANET binary availability verification
  • Required kernel module loading (vfio-pci)
  • Network interface binding to DPDK drivers
  • YANET dataplane service startup with background execution
  • YANET controlplane service startup and readiness verification
  • Service health checks and log monitoring

Parameters:

  • dataplaneConfig: YAML configuration content for the YANET dataplane service
  • controlplaneConfig: YAML configuration content for the YANET controlplane service

Returns:

  • error: An error if any step of the YANET startup process fails

Example:

dataplaneYAML := `
interfaces:
  - name: "eth0"
    pci: "01:00.0"
`
controlplaneYAML := `
modules:
  - name: "forward"
`
if err := fw.StartYANET(dataplaneYAML, controlplaneYAML); err != nil {
    log.Fatalf("YANET startup failed: %v", err)
}

func (*TestFramework) Stop

func (f *TestFramework) Stop() error

Stop performs cleanup of the test environment, closing socket clients and stopping the QEMU virtual machine.

Multiple cleanup errors are collected and returned as a combined error.

func (*TestFramework) Unmount9P

func (f *TestFramework) Unmount9P() error

Unmount9P unmounts all 9P shares inside the guest VM. This removes the QEMU migration blockers so that savevm/loadvm can proceed. When PrepareLocalStorage() has been called first, no process holds open fids on 9P mounts and plain umount works. Log tailer processes (tail -f >> /mnt/logs/...) are killed first because they hold open fids on /mnt/logs via write-append.

func (*TestFramework) WaitForDatapathReady

func (f *TestFramework) WaitForDatapathReady(timeout time.Duration) error

WaitForDatapathReady sends ICMP heartbeat packets until the dataplane responds, confirming DPDK virtio-user reconnect after a snapshot restore. No operstate check — on macOS TCG and Linux KVM, kni0 operstate stays DOWN even when DPDK is actively forwarding. Only an end-to-end packet test works.

func (*TestFramework) WaitForReady

func (f *TestFramework) WaitForReady(timeout time.Duration) error

WaitForReady blocks until the QEMU virtual machine becomes ready for command execution or the specified timeout expires. This is a proxy method for QEMU.WaitForReady.

Parameters:

  • timeout: Maximum time to wait for VM readiness

Returns:

  • error: An error if the timeout expires before VM becomes ready, or nil if ready

Example:

if err := fw.WaitForReady(60 * time.Second); err != nil {
    log.Fatalf("VM failed to become ready: %v", err)
}

func (*TestFramework) WaitOutputPresent

func (f *TestFramework) WaitOutputPresent(cmd string, checker func(string) bool, timeout time.Duration) error

WaitOutputPresent repeatedly executes a command until the output satisfies the provided checker function or the timeout expires. This utility method is used for waiting on asynchronous operations and service readiness verification.

The method polls the command output at regular intervals (100ms) and applies the checker function to determine if the expected condition has been met. This is particularly useful for waiting on service startup, configuration application, or system state changes.

Parameters:

  • cmd: Shell command to execute repeatedly
  • checker: Function that returns true when the desired condition is met
  • timeout: Maximum time to wait for the condition

Returns:

  • error: An error if the timeout expires or command execution fails

Example:

err := fw.WaitOutputPresent("ps aux | grep yanet", func(output string) bool {
    return strings.Contains(output, "yanet-dataplane")
}, 30*time.Second)

func (*TestFramework) WriteGuestFile

func (f *TestFramework) WriteGuestFile(path string, contents string) error

WriteGuestFile writes a file into the guest filesystem.

type UDPBuilder

type UDPBuilder struct {
	// contains filtered or unexported fields
}

func UDP

func UDP(opts ...UDPOption) *UDPBuilder

func (*UDPBuilder) Build

type UDPOption

type UDPOption func(*UDPBuilder)

func UDPChecksumRaw

func UDPChecksumRaw(cs uint16) UDPOption

UDPChecksumRaw sets an explicit checksum and prevents recomputation during serialization

func UDPDport

func UDPDport(port uint16) UDPOption

func UDPLengthRaw

func UDPLengthRaw(length uint16) UDPOption

UDPLengthRaw sets an explicit UDP length value from the original PCAP header. This is required for PCAP equivalence tests where we must preserve even malformed or unusual length values byte-for-byte.

func UDPSport

func UDPSport(port uint16) UDPOption

type VMPool

type VMPool struct {
	// contains filtered or unexported fields
}

VMPool manages a pool of QEMU virtual machines for parallel test execution. Each VM slot holds a long-lived QEMUManager + framework instance. Pool slots are started from a pre-prepared template overlay (typically baseline, falling back to booted) and remain running between tests. Per-test isolation is achieved via RestoreBooted(), which does a fast loadvm+reconnect without restarting the QEMU process.

Pool size is controlled by YANET_VM_POOL_SIZE (default 1).

func NewVMPool

func NewVMPool(size int, baseName string, qemuImage string, bootedTemplate string, templateOverlay string, templateSnapshotName string, enableSSHForward bool, log *zap.SugaredLogger) (_ *VMPool, err error)

NewVMPool creates a pool of size VMs. The bootedTemplate overlay is the canonical fallback source for pool slots. If templateOverlay/templateSnapshotName are provided and valid, StartAll prefers them. Otherwise it falls back to the booted template and bootstraps it if needed.

VMs are not started yet - call StartAll after creating the pool.

func (*VMPool) Acquire

func (p *VMPool) Acquire() *TestFramework

Acquire blocks until a VM slot is available and returns its framework instance. The caller MUST call Release when done.

func (*VMPool) ForEachParallel

func (p *VMPool) ForEachParallel(fn func(idx int, fw *TestFramework) error) error

ForEachParallel calls fn for each VM's framework instance in parallel.

func (*VMPool) ForceStop

func (p *VMPool) ForceStop()

ForceStop makes every VM in the pool ignore the test keep-alive setting.

func (*VMPool) Release

func (p *VMPool) Release(fw *TestFramework)

Release returns a VM slot back to the pool.

func (*VMPool) Shutdown

func (p *VMPool) Shutdown() error

Shutdown stops all VM slots in the pool.

func (*VMPool) Size

func (p *VMPool) Size() int

Size returns the number of VM slots in the pool.

func (*VMPool) StartAll

func (p *VMPool) StartAll() error

StartAll starts all VM slots. It prefers the configured template overlay when available and otherwise falls back to the cached booted template.

  • Fast path: preferred template or booted template already exists -> copy it to each slot and boot via -loadvm <snapshot>.
  • Slow path: booted template missing -> boot VM0 from scratch, save the booted snapshot, cache it as the template, then restart all slots from it.

After StartAll all slots are added to the available channel.

func (*VMPool) StopAllCPU

func (p *VMPool) StopAllCPU()

StopAllCPU pauses every VM's CPU via the QEMU monitor. Useful after initial setup to avoid idle DPDK busy-polling consuming host CPU.

func (*VMPool) WaitAllReady

func (p *VMPool) WaitAllReady(timeout time.Duration) error

WaitAllReady waits for every VM in the pool to reach the shell prompt.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL