m/test/launch/cluster: add pcap dump

Dump all network traffic by default to help debug failed tests.

Change-Id: I5466639fa00501373690bd95452b85b61fb5b172
Reviewed-on: https://review.monogon.dev/c/monogon/+/1076
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Leopold Schabel <leo@monogon.tech>
diff --git a/metropolis/test/launch/cluster/cluster.go b/metropolis/test/launch/cluster/cluster.go
index 18cab25..bd71f06 100644
--- a/metropolis/test/launch/cluster/cluster.go
+++ b/metropolis/test/launch/cluster/cluster.go
@@ -16,6 +16,7 @@
 	"net"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"syscall"
 	"time"
@@ -60,6 +61,10 @@
 	// configurations.
 	ConnectToSocket *os.File
 
+	// When PcapDump is set, all traffic is dumped to a pcap file in the
+	// runtime directory (e.g. "net0.pcap" for the first interface).
+	PcapDump bool
+
 	// SerialPort is an io.ReadWriter over which you can communicate with the serial
 	// port of the machine. It can be set to an existing file descriptor (like
 	// os.Stdout/os.Stderr) or any Go structure implementing this interface.
@@ -293,6 +298,19 @@
 		qemuArgs = append(qemuArgs, "-fw_cfg", "name=dev.monogon.metropolis/parameters.pb,file="+parametersPath)
 	}
 
+	if options.PcapDump {
+		var qemuNetDump launch.QemuValue
+		pcapPath := filepath.Join(r.ld, "net0.pcap")
+		if options.PcapDump {
+			qemuNetDump = launch.QemuValue{
+				"id":     {"net0"},
+				"netdev": {"net0"},
+				"file":   {pcapPath},
+			}
+		}
+		qemuArgs = append(qemuArgs, "-object", qemuNetDump.ToOption("filter-dump"))
+	}
+
 	// Start TPM emulator as a subprocess
 	tpmCtx, tpmCancel := context.WithCancel(options.Runtime.ctxT)
 	defer tpmCancel()
@@ -653,6 +671,7 @@
 			},
 		},
 		SerialPort: newPrefixedStdio(0),
+		PcapDump:   true,
 	}
 
 	// Start the first node.
@@ -681,6 +700,7 @@
 			ExtraNetworkInterfaces: switchPorts,
 			PortMap:                portMap,
 			SerialPort:             newPrefixedStdio(99),
+			PcapDump:               path.Join(ld, "nanoswitch.pcap"),
 		}); err != nil {
 			if !errors.Is(err, ctxT.Err()) {
 				log.Fatalf("Failed to launch nanoswitch: %v", err)
diff --git a/metropolis/test/launch/launch.go b/metropolis/test/launch/launch.go
index daf2f4b..e8df009 100644
--- a/metropolis/test/launch/launch.go
+++ b/metropolis/test/launch/launch.go
@@ -170,6 +170,10 @@
 	// that is normally the first network interface. If this is set PortMap is ignored.
 	// Mostly useful for speeding up QEMU's startup time for tests.
 	DisableHostNetworkInterface bool
+
+	// PcapDump can be used to dump all network traffic to a pcap file.
+	// If unset, no dump is created.
+	PcapDump string
 }
 
 // RunMicroVM launches a tiny VM mostly intended for testing. Very quick to boot
@@ -257,6 +261,15 @@
 			"-device", "virtio-net-device,netdev=usernet0,mac="+HostInterfaceMAC.String())
 	}
 
+	if !opts.DisableHostNetworkInterface && opts.PcapDump != "" {
+		qemuNetDump := QemuValue{
+			"id":     {"usernet0"},
+			"netdev": {"usernet0"},
+			"file":   {opts.PcapDump},
+		}
+		extraArgs = append(extraArgs, "-object", qemuNetDump.ToOption("filter-dump"))
+	}
+
 	var stdErrBuf bytes.Buffer
 	cmd := exec.CommandContext(ctx, "qemu-system-x86_64", append(baseArgs, extraArgs...)...)
 	cmd.Stdout = opts.SerialPort